/*
 * Isomorphic SmartClient
 * Version 7.2b (2009-08-08)
 * Copyright(c) 1998 and beyond Isomorphic Software, Inc. All rights reserved.
 * "SmartClient" is a trademark of Isomorphic Software, Inc.
 *
 * licensing@smartclient.com
 *
 * http://smartclient.com/license
 */

 




//>	@class	Canvas
//
//		Canvas is the base abstraction for cross-browser DHTML drawing.
//		All DHTML widgets inherit from the Canvas class.
//
//  @treeLocation Client Reference/Foundation
//  @visibility external
//<

// declare the class itself
isc.ClassFactory.defineClass("Canvas");

// for speed, override the default isA function provided by ClassFactory.  The marker property
// "_isA_Canvas" is added below as both a class and instance property.
// Note that this approach could be extended to all classes via generating unique marker
// properties (so that iterating up the inheritance chain would not be required) but that would
// slow down init time, and isA.Canvas is 99% of the critical path usage anyway
isc.isA.Canvas = function (object) { return (object != null && object._isA_Canvas); }

// define groups for documentation purposes

	//>	@groupDef positioning
	//	Horizontal and vertical location on the page
	//<
	//>	@groupDef visibility
	//	Whether an object can be seen
	//<
	//>	@groupDef sizing
	// Width and height of an object
	//<
	//>	@groupDef appearance
	// Properties defining an object's apperance
	//<
	//>	@groupDef drawing
	// Rendering an object on the page
	//<
	//>	@groupDef zIndex
	// Object's "stacking order" above or below other objects
	//<
	//>	@groupDef scrolling
	// Scrolling and clipping of objects when part of the object is obscured
	//<
	//>	@groupDef events
	// Handling mouse and keyboard events generated by the user
	//<
	//>	@groupDef containment
	// Canvases containing other objects as children or peers
	//<
	//>	@groupDef cues
	// Visual hints for the user that something can be done to this object
	//<
	//>	@groupDef dragdrop
	// Dragging objects and dropping them onto other objects
	//<
	//>	@groupDef image
	// Utilities to render images
	//<
	//>	@groupDef images
	// Refering to and loading images.
    // <P>
	// The two main URL settings relevent to loading images are:<br>
	// * imgDir (where application-specific images live)<br>
	// * skinImgDir (where system supplied images live)<br>
	//<
    //> @groupDef skins
    // Properties used to manage to the the overall appearance of the application.
    // <P>
    // A "skin" consists of 
    // <ul>
    // <li><code>skin_styles.css</code>: a .css file defining the set of 
    //     classes to apply to SmartClient components' visual elements</li>
    // <li><code>images/</code>: a directory of image files used as part of visual 
    //     components</li>
    // <li><code>load_skin.js</code>: a .js file containing overrides for various 
    //     SmartClient component properties that affect the appearance of those components.</li>
    // </ul>
    // Skins are loaded via the <code>skin</code> attribute of the +link{group:loadISCTag} or 
    // by including the appropriate <code>load_skin.js</code> source file with a standard script
    // include tag.
    // <P>
    // To create a custom skin, we suggest making a complete copy of an existing skin, then
    // modifying the media, css class definitions and component property overrides you wish to
    // change.
    // <P>
    // Note that the <code>load_skin.js</code> file contains a +link{Page.setSkinDir()} 
    // directive to set up the skin dir (used to ensure media is retrieved from the appropriate
    // directory), and a +link{Page.loadStyleSheet()} directive to load the .css file.
    // <P>
    // See the +link{group:skinning,Skinning Overview} for more information. 
    //
    // @see group:appearance
    // @see group:images
    // @see group:files
    //<
	//>	@groupDef files
	// Refering to and loading other files.
    // <P>
	// The two main URL settings relevant to file loading are:<br>
	// * appDir  (where application-specific files live)<br>
	// * isomorphicDir (where system supplied files live)<br>
	//<

	//>	@groupDef utils
	// Misc utilities
    // @visibility internal
	//<
	//>	@groupDef form
	// Utilities to deal with forms and form elements.<P>
    //
    // Internal because DynamicForm exposes the functionality we support for forms - dealing with
    // forms directly is a minefield.
    // @visibility internal
	//<
	//>	@groupDef handles
	// Pointers to the DOM structures of objects that have been drawn
    // @visibility internal
	//<


//	Add class-level properties
//		You can access these properties on the static class object.
//		e.g.,	Canvas.myStaticProperty

isc.Canvas.addClassProperties({
    

    _isA_Canvas : true,

	//	Class constants
	AUTO:"auto",				//=	@const	isc.Canvas.AUTO			The "use default" setting.
	ANYTHING:"**anything**",	//=	@const	isc.Canvas.ANYTHING		Generally means "any value is acceptible".


	//>	@type	Positioning
	//			@visibility external
	//			@group	positioning
	ABSOLUTE:"absolute",		//	@value	isc.Canvas.ABSOLUTE		The canvas is absolutely positioned with respect to its parentElement.
	RELATIVE:"relative",		//	@value	isc.Canvas.RELATIVE		The canvas is relatively positioned according to the document flow.
	//<
	
	//>	@type	Visibility
	//			@visibility external
	//			@group	visibility
	INHERIT:"inherit",			//	@value	isc.Canvas.INHERIT		The widget visibility will match that of its parent (usually visible).
	VISIBLE:"visible",			//	@value	isc.Canvas.VISIBLE		The widget will always be visible whether its parent is or not.
	HIDDEN:"hidden",			//	@value	isc.Canvas.HIDDEN		The widget will always be hidden even when its parent is visible.
	//<
    
	//>	@type	DrawnState
	//			@group	drawing
	COMPLETE:"complete",			
    //	@value	isc.Canvas.COMPLETE     the canvas is completely drawn, including children and peers, set up events, etc.
	DRAWN:"complete",			
    //	@value	isc.Canvas.DRAWN        the canvas is completely drawn (synonym for isc.Canvas.COMPLETE)
	DRAWING_HANDLE:"drawingHandle",			
    //	@value	isc.Canvas.DRAWING_HANDLE     the canvas is in the process of writing it's handle to the page / DOM
	HANDLE_DRAWN:"handleDrawn",			
    //	@value	isc.Canvas.HANDLE_DRAWN     the canvas has completely written its handle to DOM 
	UNDRAWN:"undrawn",			
    //	@value	isc.Canvas.UNDRAWN     the canvas has not been drawn
    //<
    
	//>	@type Overflow
	// @visibility external
	//			@group	sizing
	//	@value	Canvas.VISIBLE		Content that extends beyond the widget's width or height is
    //                              displayed.
    //                              Note: To have the content be sized only by the drawn size of 
    //                              the content set the overflow to be Canvas.VISIBLE and specify
    //                              a small size, allowing the size to expand to the size required
    //                              by the content.
    //                              Leaving the width / height for the widget undefined will use the
    //                              default value of 100, and setting the size to zero may cause the
    //                              widget not to draw.
	//	@value	Canvas.HIDDEN		Content that extends beyond the widget's width or height is
    //                              clipped (hidden).
	//	@value	Canvas.AUTO			Horizontal and/or vertical scrollbars are displayed only if
    //                              necessary. Content that extends beyond the remaining visible
    //                              area is clipped.
    //	@value	Canvas.SCROLL		Horizontal and vertical scrollbars are always drawn inside the
    //                              widget. Content that extends beyond the remaining visible area
    //                              is clipped, and can be accessed via scrolling.
	SCROLL:"scroll",			
    //	@value	Canvas.CLIP_H		Clip horizontally but extend the canvas's clip region
    //                              vertically if necessary.
	CLIP_H:"clip-h",			
    //	@value	Canvas.CLIP_V		Clip vertically but extend the canvas's clip region
    //                              horizontally if necessary. 
	CLIP_V:"clip-v",			
	//<

    

    //	@value	Canvas.IGNORE		Clipping is ignored by the ISC system. This setting may be used
    //                              for improved performance, with frequently-drawn widgets whose
    //                              dimensions always agree exactly with the size of their contents.
	IGNORE:"ignore",			

	//>	@type	ScrollMechanism
	//			@group	scrolling
	NATIVE:"native",
    //	@value	isc.Canvas.NATIVE   Scroll by "native" mechanism - assigning directly to scrollLeft
    //                              and scrollTop
	CLIP:"clip",				
    //	@value	isc.Canvas.CLIP     Scroll by repositioning / resizing handle and moving a clip
    //                              region as a viewport
	NESTED_DIV:"nestedDiv",				
    //	@value	isc.Canvas.NESTED_DIV   Scroll by moving a handle around within an outer handle.
    //<
    	
	//>	@type	Alignment
	CENTER:"center",			//	@value	isc.Canvas.CENTER		Center within container.
	LEFT:"left",				//	@value	isc.Canvas.LEFT			Stick to left side of container.
	RIGHT:"right",				//	@value	isc.Canvas.RIGHT		Stick to right side of container.
	// @group appearance
	// @visibility external
	//<
	
	//>	@type	VerticalAlignment
    //	@value	isc.Canvas.TOP			At the top of the container
	TOP:"top",
    //	@value	isc.Canvas.CENTER		Center within container.
    //	@value	isc.Canvas.BOTTOM       At the bottom of the container
	BOTTOM:"bottom",			
	// @group appearance
	// @visibility external
	//<

    //> @type Side
    // Side of a component.
	//	@value	isc.Canvas.LEFT			Left side
	//	@value	isc.Canvas.RIGHT		Right side
    //	@value	isc.Canvas.TOP			Top side
    //	@value	isc.Canvas.BOTTOM       Bottom side
	// @visibility external
    //<
	

	//>	@type	Direction
	//			@visibility external
	//			@group	appearance
    //	@value	isc.Canvas.UP           above
	UP:"up",

    //	@value	isc.Canvas.DOWN         below
	DOWN:"down",				
    //	@value	Canvas.LEFT			to the left of
    //	@value	Canvas.RIGHT		to the right of
	//<

    // other generic constants
    BOTH:"both",
    NONE:"none",
    VERTICAL:"vertical",
    HORIZONTAL:"horizontal",
    // layoutResizeBarPolicy constants
    MARKED:"marked",
    MIDDLE:"middle",
    ALL:"all",
    
	//>	@type	Cursor
    // 
    // You can use whatever cursors are valid for your deployment platforms, but keep in mind that
    // visual representation may vary by browser and OS.
    //
    //	@value	Canvas.DEFAULT		Use the default arrow cursor for this browser/OS.  
    //  @value  Canvas.AUTO         Use the default cursor for this element type in this browser/OS
    //	@value	Canvas.WAIT         Use the wait cursor.
    //	@value	Canvas.HAND			Use the hand cursor.
    //	@value	Canvas.MOVE			Use the "move" (crosshairs) cursor.
    //	@value	Canvas.HELP			Use the 'help' cursor.
    //	@value	Canvas.TEXT			Use the 'text' (i-beam) cursor.	
    //	@value	Canvas.CROSSHAIR	Use the 'crosshair' ( + ) cursor.	
    //	@value	"col-resize" 	    Use the column resize cursor (horizontal double-tipped arrow)
    //	@value	"row-resize" 	    Use the row resize cursor (vertical double-tipped arrow)
    //	@value	"e-resize" 	        Use the "east resize" cursor.
    //	@value	"w-resize" 	        Use the "west resize" cursor.
    //	@value	"n-resize" 	        Use the "north resize" cursor.
    //	@value	"s-resize" 	        Use the "south resize" cursor.
    //	@value	"se-resize" 	    Use the "south-east resize" cursor.
    //	@value	"ne-resize" 	    Use the "north-east resize" cursor.
    //	@value	"nw-resize" 	    Use the "north-west resize" cursor.
    //	@value	"sw-resize" 	    Use the "south-west resize" cursor.
    //  @value  "not-allowed"       Use the "not-allowed" cursor.
    //
	//  @group	cues
	//	@see	attr:Canvas.cursor
    //
	//  @visibility external
    //  @example cursors
    //<
    //	@value	Canvas.ARROW		Synonym for "default"
    // NOTE: there is a difference between Canvas.DEFAULT and Canvas.AUTO - auto is as if there
    // was no cursor specified - if the element has text in it, the I-Beam text selection cursor
    // will show up when the user rolls over it. If the cursor is set to 'default' however the
    // cursor will show the standard default cursor (arrow) over the entire element.
	DEFAULT:"default",
	ARROW:"default",

	WAIT:"wait",				
	HAND:(isc.Browser.isMoz ? "pointer" : "hand"),	
	MOVE:"move",				
	HELP:"help",				
	TEXT:"text",				
	CROSSHAIR:"crosshair",	
    // Used for no-drop indication - not supported on Safari
    
    NOT_ALLOWED:"not-allowed",	

    // NOTE: e-resize means "east resize".  On Windows, there's no distinction between east and west
    // resize (it's just horizontal resize), but on some OS' it may look directional, so we may need
    // to add a conditional to fall back to the "move" cursor on non-Windows platforms.
	COL_RESIZE:(isc.Browser.isIE && isc.Browser.version >= 6 ? "col-resize" : "e-resize"), 

	ROW_RESIZE:(isc.Browser.isIE && isc.Browser.version >= 6 ? "row-resize" : "n-resize"),

	//>	@type	ImageStyle
	//			@visibility external
	//			@group	appearance
    //	@value	isc.Canvas.CENTER	Center (and don't stretch at all) the image if smaller than its enclosing frame.
	//CENTER:"center",
	//	@value	isc.Canvas.TILE		Tile (repeat) the image if smaller than its enclosing frame.
	TILE:"tile",
	//	@value	isc.Canvas.STRETCH	Stretch the image to the size of its enclosing frame.
	STRETCH:"stretch",
	//	@value	isc.Canvas.NORMAL   Allow the image to have natural size
	NORMAL:"normal",
	//<

	//>	@type	Canvas.BkgndRepeat
	//			@group	appearance
	//			@see	canvas.backgroundRepeat
	REPEAT:"repeat", 			//	@value	isc.Canvas.REPEAT		Tile the background image horizontally and vertically.
	NO_REPEAT:"no-repeat",		//	@value	isc.Canvas.NO_REPEAT	Don't tile the background image at all.
	REPEAT_X:"repeat-x", 		//	@value	isc.Canvas.REPEAT_X		Repeat the background image horizontally but not vertically.
	REPEAT_Y:"repeat-y",		//	@value	isc.Canvas.REPEAT_Y		Repeat the background image vertically but not horizontally.
	//<

	//>	@type	Canvas.TextDirection
	//		Specifies RTL or LTR direction for text -- IE 5+ only
	//		Specify this to have your text show up "right to left" (rtl), eg: in Arabic or Hebrew
	//		Note: more efficient to leave blank for default of "left to right" (ltr)
	//	@group	appearance
	LTR:"ltr",		 		//	@value	isc.Canvas.LTR		Show text left-to-right (eg: English)
	RTL:"rtl",				//	@value	isc.Canvas.RTL		Show text right-to-left (eg: Arabic)
	//<
		
	//>	@type	Canvas.SnapDirection
	//		Specifies which direction to snap to, when snap-to-grid is enabled
	//	@group	dragdrop
	BEFORE:"before",    //  @value  isc.Canvas.BEFORE   Always snap up or left
	AFTER:"after",      //  @value  isc.Canvas.AFTER    Always snap down or right
	NEAREST:"nearest",  //  @value  isc.Canvas.NEAREST  Snap to the nearest grid point
	//<
		
	//>	@type	Canvas.SnapAxis
	//		Specifies which axis or axes we consider when snap-to-grid is enabled
	//	@group	dragdrop
	                    //  @value  isc.Canvas.HORIZONTAL   Snap on the horizontal axis
	                    //  @value  isc.Canvas.VERTICAL     Snap on the horizontal axis
	                    //  @value  isc.Canvas.BOTH         Snap on both axes
	//<


    
    

	// default zIndex for the next item to be drawn
	_nextZIndex:200000,

	// zIndex of the next item to be sent to the back
	_SMALL_Z_INDEX:199950,

	// zIndex of the next item to be brought to the front
	_BIG_Z_INDEX:800000,


	//>	@classAttr isc.Canvas.TAB_INDEX_GAP (integer : 80 : R)
	//		Specifies the gap to leave between automatically assigned tab indices for focusable 
    //      canvii
    //<
    TAB_INDEX_GAP:50,

	//>	@classAttr isc.Canvas.TAB_INDEX_FLOOR (integer : 1000 : R)
	//		Specifies the lower limit for automatically assigned tab indices for focusable canvii.
    // @group focus
    // @visibility external
    //<
    TAB_INDEX_FLOOR:1000,
    
    //> @classAttr isc.Canvas.TAB_INDEX_CEILING (integer : 32766 : RA)
    // This is the native browser upper limit for tabIndices
    // @visibility internal
    //<
    
    TAB_INDEX_CEILING:32766,

    //>	@classAttr	isc.Canvas._imageCache		(array : [] : IRWA)
	//			cache to hold images, so we avoid loading them over and over unecessarily
	//		@group	images
	//<
	_imageCache:[],						

    // List of CSS attributes that apply to text only:
    textStyleAttributes : [ "fontFamily", "fontSize", "color", "backgroundColor", 
                            "fontWeight", "fontStyle", "textDecoration", "textAlign"
                            // Optionally also include: fontSizeAdjust, fontVariant, whiteSpace
                          ],
    			

    // No style doubling
    // In various places we render widgets as a table nested inside the handle
    // In this case we have to re-apply the css class applied to the widget to the TD as otherwise
    // text based styling options will not be applied.
    // However we DON'T want to re-apply every property, otherwise we end up with (for example)
    // borders around the widget and additional borders around the table cell.
    // We usually handle this by writing out explicit "null" styling options on the TD to 
    // override the properties we don't want doubled from the css class. These options then take
    // presidence over the attributes specified in the CSS class.
    // Use this central string to clear out 
    // - margin, border, padding, bg color, filter, background-image
    _$noStyleDoublingCSS:(!isc.Browser.isIE ? "margin:0px;border:0px;padding:0px;background-image:none;background-color:transparent;" 
                        // Also explicitly clear filter in IE
                          : "margin:0px;border:0px;padding:0px;background-color:transparent;filter:none;background-image:none;"),
    
    

    // Delayed Redraw
	// -----------------------------------------------------------------------------------------
    //>	@classAttr	isc.Canvas._redrawQueue		(array of canvas objects : [] : IRWA)
	//			array to hold pointers to canvases that need to be redrawed
	//			these items will be redrawn automatically after "a little while"
	//		@group	handles
	//		@see	Canvas.clearRedrawQueue()
	//<
	_redrawQueue:[],					

    //>	@classAttr	Canvas._redrawQueueDelay		(number : 0 : IRWA)
	//			(msec) delay after which canvases that need to be redrawn are actually redrawn
	//		@group	handles
	//		@see	Canvas.clearRedrawQueue()
	//<
    // NOTE: redraws are generally done on a timer because it batches many changes which require
    // redraws into a single redraw.  Redraws can be done immediately, in specific circumstances
    // like drag resizing, in order to provide more immediate response.
    _redrawQueueDelay:(0),
    _delayedAdjustOverflowQueueDelay:200,    

    // Stats and global Canvas tracking
	// -----------------------------------------------------------------------------------------
    //>	@classAttr	isc.Canvas._canvasList		(array of canvas objects : [] : IRWA)
	//			array to hold pointers to all of the canvases that have been created
	//			so we can clear them out later
	//		@group	handles
	//		@platformNotes	Used in IE only to clear all handles when the page is unloaded.
	//		@see	Canvas._clearDOMHandles()
	//<
	_canvasList:[],	
										
    // count of canvases which are flagged as _iscInternal used e.g. in the Developer Console to
    // report the end-user canvas count number
    _iscInternalCount: 0,

    // object where we record/update various statistics
    _stats : {
        redraws:0,
        clears:0,
        destroys:0,
        draws:0
        // NOTE: number allocated is just Canvas._canvasList.length
    },
    // object for tracking redraws by widget ID
    _redraws : {
    },
    
    // cache for partwise-event handler names
    _partHandlers : {},

    // Wrapping HTML in Canvii
	// -----------------------------------------------------------------------------------------
    _wrapperCanvasStack : [],
    
    
    useMozBackMasks : false,
    
    //> @classAttr canvas.useNativeWheelDelta (boolean : true: RWA)
    // If set, use the magnitude of the wheel delta as reported by the browser
    // to estimate the OS wheel sensitivity setting. Currently, this only applies
    // to Firefox, and controls whether or not SCROLL_PAGE_UP or SCROLL_PAGE_DOWN
    // events are parsed (a facility which is wholly undocumented). All other browsers
    // return wheelDeltas in multiples of 120, depending on how fast the user
    // scrolls the wheel, and regardless of OS sensitivity settings.
    // @group scrolling
    // @visibility internal
    //<
    useNativeWheelDelta: true,
    
    //> @classAttr canvas.scrollWheelDelta (number : 50 : RWA)
    // 
    // Sensitivity of the mouse scroll wheel, in pixels. <p> The exact behavior of this
    // setting is browser-dependent. For most browsers,
    // this is the sole option controlling the sensitivity of the scroll wheel.
    // For Firefox, this represents the scroll wheel distance when the OS is configured
    // for a wheel sensitivity of 3 lines. If the sensitivity is increased or reduced, 
    // the scroll scroll distance will be equal to scrollWheelDelta * (lines/3).
    // If the OS is set to page-at-a-time scrolling, for Firefox, the distance scrolled is the 
    // height of the window, rounded down to a multiple of scrollWheelDelta. 
    // @group scrolling
    //<
    scrollWheelDelta: 50
});


isc.Canvas.addProperties({

    _isA_Canvas : true,

	//> @attr	canvas.ID		(string : null : IR)
	// Global identifier for referring to a widget in JavaScript.  The ID property is optional if
    // you do not need to refer to the widget from JavaScript, or can refer to it indirectly
    // (for example, by storing the reference returned by +link{class.create,create()}).
    // <P>
    // An internal, unique ID will automatically be created upon instantiation for any canvas
    // where one is not provided. 
    // <P>
    // The ID property should be unique in the global scope. If <code>window[<i>ID</i>]</code> 
    // is already assigned to something else a warning will be logged using the developer console,
    // and the existing reference will be replaced, calling +link{Canvas.destroy(),destroy()} on the
    // previous object if it is a SmartClient Class instance.
    // <P>
    // Automatically generated IDs will be unique as long as the canvases they refer to remain
    // active - once a canvas with an automatically generated ID has been destroyed, its ID may be
    // reused for the next canvas created with no explicitly specified ID.
    //
    // @group basics
    // @visibility external
	//<										
		
    //>	@attr	canvas.autoDraw		(boolean : true : IR)
    // If true, this canvas will draw itself immediately after it is created.
    // <P>
    // <b>Note</b> that you should turn this OFF for any canvases that are provided as children
    // of other canvases, or they will draw initially, then be clear()ed and drawn again when
    // added as children, causing a large performance penalty.  
    // <P>
    // For example, the following code is incorrect and will cause extra draw()s:
    // <P>
    // <pre>
    //     isc.Layout.create({
    //         members : [
    //             isc.ListGrid.create()
    //         ]
    //     });
    // </pre>
    // It should instead be:
    // <pre>
    //     isc.Layout.create({
    //         members : [
    //             isc.ListGrid.create(<b>{ autoDraw: false }</b>)
    //         ]
    //     });
    // </pre>
    // In order to avoid unwanted autoDrawing systematically, it is recommend that you call
    // +link{classMethod:isc.setAutoDraw(),isc.setAutoDraw(false)} immediately after SmartClient is loaded
    // and before any components are created, then set <code>autoDraw:true</code> or call
    // draw() explicitly to draw components.  
    // <P>
    // Otherwise, if the global setting for autoDraw remains <code>true</code>, you must set
    // autoDraw:false, as shown above, on every component in your application that 
    // should not immediately draw: all Canvas children, Layout members, Window items, Tab
    // panes, etc, however deeply nested.  Forgetting to set autoDraw:false will result in one
    // more clear()s - these are reported on the Results tab of the 
    // +link{group:debugging,Developer Console}, and can be tracked to individual components by
    // using the "clears" log category in the Developer Console.
    //
    //  @example autodraw
    //  @visibility external
    //  @group	drawing
    //<
    autoDraw:true,
 
    // Children and Peers   
	// --------------------------------------------------------------------------------------------

    //> @attr   canvas.parentElement    (Canvas : null : [IRA])
    // This Canvas's immediate parent, if any.
    // <BR>
    // Can be initialized, but any subsequent manipulation should be via 
    // +link{canvas.addChild(),addChild()} and +link{canvas.removeChild(),removeChild()} calls
    // on the parent.
    // 
    //  @visibility external
    //  @group  containment
    //<
    
    //> @attr   canvas.topElement    (Canvas : null : [RA])
    // The top-most Canvas (i.e., not a child of any other Canvas), if any, in this widget's
    // containment hierarchy.
    //  @visibility external
    //  @group  containment
    //<
    
    //> @attr   canvas.masterElement    (Canvas : null : [RA])
    // This Canvas's "master" (the Canvas to which it was added as a peer), if any.
    //  @visibility external
    //  @group  containment
    //<
    
    //> @attr   canvas.children    (Array of Canvas : null : IRWA)
    // Array of all Canvii that are immediate children of this Canvas.
    //  @visibility external
    //  @group  containment
    //<
    
    //> @attr   canvas.peers    (Array of Canvas : null : IRWA)
    // Array of all Canvii that are peers of this Canvas.
    //  @visibility external
    //  @group  containment
    //<
    
    //> @attr   canvas.allowContentAndChildren (boolean : false : [IA])
    // If true this widget supports having content specified via the content property and
    // children specifed in the normal way. Enabling entails a small performance reduction.
    // @visibility internal
    //<

    //> @attr   canvas.drawChildrenThenContent (boolean : false : [IA])
    // If true, and this widget supports having content and children, when this widget is
    // drawn, the children will be written into the handle, then the content will be created
    // and inserted before the first child in the DOM.
    // @visibility internal
    //<

    // --------------------------------------------------------------------------------------------
    
    //> @pseudoClass DrawContext
    // Object that expresses the position in the DOM where a Canvas should draw itself, used
    // for insertion into an existing DOM structure.
    // @treeLocation Client Reference/Foundation/Canvas
    // @group drawContext
    // @visibility drawContext
    //<
    
    //> @attr drawContext.element (DOMElement : null : [IRA])
    // Element in the DOM
    // @group drawContext
    // @visibility drawContext
    //<

    //> @attr drawContext.position (DrawPosition : "beforeBegin" : [IRA])
    // Position where Canvas should be inserted relative to <code>drawContext.element</code>.
    // @group drawContext
    // @visibility drawContext
    //<

    //> @type DrawPosition
    // @value "beforeBegin" insert before the target element
    // @value "afterBegin"  insert as the target element's first child
    // @value "beforeEnd"   insert as the target element's last child
    // @value "afterEnd"    insert after the target element
    // @value "replace"     replace the target element
    // @group drawContext
    // @visibility external
    //<

    //> @attr  canvas.drawContext (DrawContext : null : [IRWA])
    // Location in the DOM where this Canvas should draw itself, specified as an existing DOM
    // element and a position relative to that element.
    // <P>
    // This feature is intended for temporary integration with legacy page architectures only;
    // the native browser's reaction to DOM insertion is unspecified and unsupported.  For
    // consistent cross-browser layout and positioning semantics, use Canvas parents
    // (especially Layouts) and use absolute positioning at top level.
    // <P>
    // NOTE: persistence of drawContext: if a Canvas is clear()d and then draw()n again, it will
    // keep the same drawContext unless the <code>drawContext.position</code> was "replace".<P> 
    // If a Canvas is added as a child to Canvas parent, its drawContext will be dropped.
    // @group drawContext
    // @visibility drawContext
    //<

    
    
    // HTMLElement
    //> @attr canvas.htmlElement (HTML Element : null : IRWA) 
    // If specified as a pointer to an HTML element present in the DOM, this canvas will be
    // rendered inside that element on +link{Canvas.draw(), draw()}.
    // <P>
    // <i>NOTES:</i><br>
    // This feature is intended for temporary integration with legacy page architectures only;
    // the native browser's reaction to DOM insertion is unspecified and unsupported. For
    // consistent cross-browser layout and positioning semantics, use Canvas parents
    // (especially Layouts) and use absolute positioning at top level.
    // <P>
    // Persistence of htmlElement: If +link{canvas.htmlPosition} is set to <code>"replace"</code>
    // the htmlElement will be removed from the DOM when the canvas is drawn - therefore the
    // htmlElement attribute will be cleared at this time.
    // Otherwise if a Canvas is clear()d and then draw()n again it will 
    // be rendered inside the same htmlElement.<br>
    // If a Canvas is added as a child to Canvas parent, its htmlElement will be dropped.
    // <P>
    // +link{canvas.position} should typically be set to <code>"relative"</code> if the widget 
    // is to be rendered inline within a standard page. 
    // @group htmlElement, positioning
    // @visibility external
    //<
    
    
    //> @attr canvas.htmlPosition (DrawPosition : "afterBegin" : [IRWA])
    // If +link{canvas.htmlElement} is specified, this attribute specifies the position where 
    // the canvas should be inserted relative to the <code>htmlElement</code> in the DOM.
    // @group htmlElement, positioning
    // @visibility external
    //<
    htmlPosition:"afterBegin",
    
    //> @attr canvas.matchElement (boolean : null : [IRWA])
    // If +link{canvas.htmlElement} is specified, should this canvas initially be drawn
    // at the same dimensions as the htmlElement?<br>
    // Note: setting this property will not force the canvas to resize if the element
    // subsequently resizes (for example due to page reflow).
    // @visibility external
    //<
    
    // Positioning
	// --------------------------------------------------------------------------------------------
    
    //>	@attr	canvas.position		(Positioning : null : IRWA)
    // Absolute or relative, corresponding to the "absolute" (with respect to parent) or
    // "relative" (with respect to document flow) values for the CSS position attribute.
    // <P>
    // Setting <code>position:"relative"</code> enables SmartClient components to be embedded
    // directly into the native HTML flow of a page, causing the component to be rendered 
    // within an existing DOM structure. 
    // This attribute should only be set to <code>"relative"</code> on a top level component 
    // (a component with no +link{canvas.parentElement}). 
    // <P>
    // There are 2 ways to embed relatively positioned canvases in the DOM - by default the
    // component will be written out inline when it gets +link{canvas.draw(),drawn()n}. For example
    // to embed a canvas in an HTML table you could use this code: 
    // <pre>
    // &lt;table&gt;
    //   &lt;tr&gt;
    //     &lt;td&gt;
    //       &lt;script&gt;
    //         isc.Canvas.create({autoDraw:true, backgroundColor:"red", position:"relative"});
    //       &lt;/script&gt;
    //     &lt;td&gt;
    //   &lt;/tr&gt;
    // &lt;/table&gt;
    // </pre>
    // Alternatively you can make use of the +link{canvas.htmlElement} attribute.
    // <P>
    // Relative positioning is intended as a short-term integration scenario while incrementally
    // upgrading existing applications.
    // Note that relative positioning is not used to manage layout within SmartClient components -
    // instead the +link{class:Layout} class would typically be used.
    // For best consistency and flexibility across browsers, all SmartClient layout managers
    // use absolute positioning.
    // <P>
    // For canvases with a specified +link{canvas.htmlElement}, this attribute defaults to
    // <code>"relative"</code>. In all other cases the default value will be 
    // <code>"aboslute"</code>.
    //
    // @visibility external
    // @group positioning
    // @example inlineComponents
    //<
	position:null,

    //>	@attr canvas.left (Number or String : 0 : IRW)
    // Number of pixels the left side of the widget is offset to the right from its default
    // drawing context (either its parent's topleft corner, or the document flow, depending on
    // the value of the +link{position} property).
    // <P>
    // Can also be set as a percentage, specified as a String ending in '%', eg, "50%".  In
    // this case the top coordinate is considered as a percentage of the specified width of
    // the +link{canvas.parentElement,parent}.
    //
    // @visibility external
    // @group  positioning
    //<
	left:0,

    //>	@attr canvas.top (Number or String : 0 : IRW)
    // Number of pixels the top of the widget is offset down from its default drawing context
    // (either its parent's top-left corner, or the document flow, depending on the value of
    // the +link{position} property).
    // <P>
    // Can also be set as a percentage, specified as a String ending in '%', eg, "50%".  In
    // this case the top coordinate is considered as a percentage of the specified height of
    // the +link{canvas.parentElement,parent}.
    //
    // @visibility external
    // @group  positioning
    //<
	top:0,

    // Sizing
	// --------------------------------------------------------------------------------------------

    // Notes on width/height vs defaultWidth/defaultHeight:
    //
    // Layouts will resize widgets that don't have their height/width explicitly set.
    // Important to use defaultHeight/defaultWidth to set defaults for a widget, otherwise the
    // defaults will be taken to be the "fixed" size of the widget, and its size will not be
    // managed by layouts
    // In some cases, we WANT certain dimensions to be regarded as fixed by a Layout (they may
    // still be overriden by the user) so we set the height/width properties

    //>	@attr	canvas.width		(Number or String : null : [IRW])
    // Size for this component's horizontal dimension.
    // <P>
    // Can be a number of pixels, or a percentage like "50%". Percentage sizes are resolved to
    // pixel values as follows:
    // <UL>
    // <LI>If a canvas has a specified +link{canvas.percentSource,percentSource}, sizing will be
    //     a percentage of the size of that widget (see also +link{canvas.percentBox}).</LI>
    // <LI>Otherwise, if a canvas has a +link{canvas.masterElement,masterElement}, and
    //     +link{Canvas.snapTo,snapTo} is set for the widget, sizing will be a percentage of
    //     the size of that widget (see also +link{canvas.percentBox}).</LI>
    // <LI>Otherwise if this is a child of some other canvas, percentages will be based on the 
    //     inner size of the +link{canvas.parentElement,parentElement}'s viewport.</LI>
    // <LI>Otherwise, for top level widgets, sizing is calculated as a percentage of page size.</LI>
    // </UL>
    // <P>
    // +link{Layout,Layouts} may specially interpret percentage sizes on their children,
    // and also allow "*" as a size.
    // <P>
    // Note that if +link{Canvas.overflow,overflow} is set to "visible", this size is a
    // minimum, and the component may overflow to show all content and/or children.
    // <P>
    // If trying to establish a default width for a custom component, set
    // +link{Canvas.defaultWidth,defaultWidth} instead.
    //
    //  @visibility external
    //  @group	sizing
    //  @setter setWidth
    //  @getter getWidth
    //<

    //>	@attr	canvas.height		(Number or String : null : [IRW])
    // Size for this component's vertical dimension.
    // <P>
    // Can be a number of pixels, or a percentage like "50%". See documentation for
    // +link{canvas.width} for details on who percentage values are resolved actual size.
    // <P>
    // Note that if +link{Canvas.overflow,overflow} is set to "visible", this size is a
    // minimum, and the component may overflow to show all content and/or children.
    // <P>
    // If trying to establish a default height for a custom component, set
    // +link{Canvas.defaultHeight,defaultHeight} instead.
    //
    //  @visibility external
    //  @group	sizing
    //  @setter setHeight
    //  @getter getHeight
    //<
    
    
    //>	@attr canvas.defaultWidth (Number : 100 : IRWA)
    // For custom components, establishes a default width for the component.
    // <P>
    // For a component that should potentially be sized automatically by a Layout, set this
    // property rather than +link{width} directly, because Layouts regard a width setting as
    // an explicit size that shouldn't be changed.
    //
    //  @visibility external
	//  @group	sizing
	//<
    defaultWidth:100,

    //>	@attr canvas.defaultHeight (Number : 100 : IRWA)
    // For custom components, establishes a default height for the component.
    // <P>
    // For a component that should potentially be sized automatically by a Layout, set this
    // property rather than +link{height} directly, because Layouts regard a height setting as
    // an explicit size that shouldn't be changed.
    //
    //  @visibility external
	//  @group	sizing
	//<
    defaultHeight:100,

    //>	@attr	canvas.minWidth		(number : 10 : IRWA)
    // Minimum width that this Canvas can be resized to.
    // <P>
    // Note that a Canvas with overflow:"visible" has an implicit minimize size based on it's
    // contents.
    //
    //  @visibility external
	//  @group	sizing
	//<
	minWidth:10,

    //>	@attr	canvas.maxWidth		(number : 10000 : IRWA)
    // Maximum width that this Canvas can be resized to.
    //
    //  @visibility external
	//  @group	sizing
	//<
	maxWidth:10000,

    //>	@attr	canvas.minHeight		(number : 10 : IRWA)
    // Minimum height that this Canvas can be resized to.
    // <P>
    // Note that a Canvas with overflow:"visible" has an implicit minimize size based on it's
    // contents.
    //
    //  @visibility external
	//  @group	sizing
	//<
	minHeight:10,

    //>	@attr	canvas.maxHeight		(number : 10000 : IRWA)
    // Maximum height that this Canvas can be resized to.
    //
    //  @visibility external
	//  @group	sizing
	//<
	maxHeight:10000,

	// --------------------------------------------------------------------------------------------

	//>	@attr	canvas.allowNativeContentPositioning    (boolean : false : IRW)
    //  Allow HTML content which includes native elements that use relative or absolute positioning.
    //<
    

	//>	@attr	canvas.zIndex		(number : Canvas.AUTO | Canvas.AUTO : IRWA)
    //
    // Stacking order of this Canvas with respect to other content and components on the page.
    //
    // The default zIndex of "auto" means that a zIndex will be decided at draw time,
    // so that if many Canvii are draw with zIndex "auto", the last Canvas drawn is on top.
    // <p>
    // If you want native HTML content to appear in front of this canvas, set zIndex to zero.
    //
	// @group	zIndex
	// @value	(number)
	// @value	Canvas.AUTO
	//<
	zIndex:isc.Canvas.AUTO,

    //>	@attr	canvas.autoShowParent		(boolean : false : IRWA)
    //      If set to true, the widget's parent (if any) will automatically be shown whenever the
    //      widget is shown.
    //  @visibility external
    //  @group appearance
    //<
	autoShowParent:false,

    //>	@attr	canvas.visibility		(Visibility : isc.Canvas.INHERIT : IRW)
    //      Controls widget visibility when the widget is initialized. See Visibility type for
    //      details.
    //  @getter isVisible
    //  @setter show, hide
    //  @visibility external
    //  @group appearance
    //<
	visibility:isc.Canvas.INHERIT,

    //>	@attr	canvas.canSelectText		(boolean : false : IRWA)
	// Whether native drag selection of contained text is allowed within this Canvas.
    // <P>
    // Note that setting this property to <code>false</code> will not avoid text selection
    // which is initiated outside this Canvas from continuing into this Canvas, even if text
    // selection began in another Canvas.
    //
	//		@group	events
    // @visibility external
	//<

	//>	@type	CSSStyleName
    // CSS class name to apply to some HTML element on this page. This is a string that should
    // match the css class defined for the page in an external stylesheet or in inline 
    // html &lt;STYLE&gt; tags.
    // <P>
    // As a general rule, wherever it is possible to provide a CSS styleName (such as
    // +link{Canvas.styleName} or +link{Button.baseStyle}, your CSS style can specify border,
    // margins, padding, and any CSS attributes controlling background or text styling.  You
    // should not specify any CSS properties related to positioning, clipping, sizing or
    // visibility (such as "overflow", "position", "display", "visibility" and "float"), using
    // the SmartClient APIs for this kind of control.
    // <P>
    // Because text wrapping cannot be consistently controlled cross-browser from CSS alone,
    // you should use SmartClient properties such as +link{Button.wrap} instead of the
    // corresponding CSS properties, when provided.
    // <P>
    // Content contained within SmartClient components can use arbitrary CSS, with the 
    // caveat that the content should be tested on all supported browsers, just as content
    // outside of SmartClient must be.
    // 
	//	@group	appearance
	//	@visibility external
    //  @example consistentSizing
    //<
    
    //>	@attr	canvas.className		(CSSStyleName : "normal" : [IRW])
    //      The CSS class applied to this widget as a whole.
    //  @visibility external
    //  @group appearance
    // @deprecated In favor or +link{canvas.styleName} as of SmartClient release 5.5
    //<
    
    //>	@attr	canvas.styleName    (CSSStyleName : "normal" : [IRW])
    //      The CSS class applied to this widget as a whole.
    //  @group appearance
    //  @setter canvas.setStyleName()
    //  @visibility external
    //  @example styles
    //<
	styleName:"normal",

	//>	@attr	canvas.textDirection	(TextDirection : null : IRW)
	//			Use this to specify a text direction for the canvas: 
	//					Canvas.LTR (left to right, eg English)
	//					Canvas.RTL (right to left, eg Arabic)
	//			Leave as null to pick up the text direction automatically
	//			 from that set at the Page level, set to one of the above to override.
	//		@group	textDirection
	//		@platformNotes	IE only.
	//<

	//>	@attr	canvas.eventProxy		(canvas object : null : IRWA)
	//			set to another canvas to have that process events for us
	//			useful for event processing of peers (borders, decorators, etc.)
	//		@group	events
	//<

    //>	@attr	canvas.contents		(string : "&nbsp;" : IRWA)
    // The contents of a canvas or label widget. Any HTML string is acceptable.
    //
    // @see dynamicContents
    // @group contents
    // @visibility external
    //<
	contents:isc.nbsp,

    //> @attr canvas.dynamicContents (boolean : false : IRWA)
    //
    // Dynamic contents allows the contents string to be treated as a simple, but powerful
    // template.  When this attribute is set to true, expressions of the form \${arbitrary JS
    // here} are replaced by the result of the evaluation of the JS code inside the curly
    // brackets.  This evaluation happens at draw time.  If you want to trigger a re-evaluation
    // of the expressions in the contents string you can call markForRedraw() on the canvas.
    // <p>
    // You can use this feature to build some simple custom components. For example, let's say
    // you want to show the value of a Slider in a Canvas somewhere on the screen.  You can do
    // this by observing the valueChanged() method on the slider and calling setContents() on
    // your canvas with the new string or you can set the contents of the canvas to something
    // like:
    // <p><code>
    // "The slider value is \${sliderInstance.getValue()}."
    // </code><p>
    // Next you set dynamicContents: true on the canvas, observe valueChanged() on the slider
    // and call canvas.markForRedraw() in that observation.  This approach is cleaner than
    // setContents() when the Canvas is aggregating several values or dynamic expressions.
    // Like so:
    // <p>
    // <pre>
    // Slider.create({
    //     ID: "mySlider"
    // });
    //
    // Canvas.create({
    //     ID: "myCanvas",
    //     dynamicContents: true,
    //     contents: "The slider value is \${mySlider.getValue()}."
    // });
    //     
    // myCanvas.observe(mySlider, "valueChanged", 
    //                  "observer.markForRedraw()");
    // </pre>
    // You can embed an arbitrary number of dynamic expressions in the contents string.  The
    // search and replace is optimized for speed.
    // <p>
    // If an error occurs during the evaluation of one of the expressions, a warning is logged
    // to the ISC Developer Console and the error string is embedded in place of the expected
    // value in the Canvas.
    // <p>
    // The value of a function is its return value.  The value of any variable is the same as
    // that returned by its toString() representation.
    // <p>
    // Inside the evalution contentext, <code>this</code> points to the canvas instance that
    // has the dynamicContents string as its contents - in other words the canvas instance on
    // which the template is declared.
    //
    // @see contents
    // @see canvas.dynamicContentsVars
    // @example dynamicContents
    // @group contents
    // @visibility external
	//<

    //> @attr canvas.dynamicContentsVars (ValueMap : null : IRWA)
    //
    // An optional map of name:value parameters that will be evailable within the scope of the
    // dynamicContents evaluation.  For example - if you have e.g:
    // <pre>
    // Canvas.create({
    //   dynamicContents: true,
    //   dynamicContentsVars: {
    //       name: "Bob"
    //   },
    //   contents: "hello \${name}"
    // });
    // </pre>
    // The above will create a canvas with contents <code>hello Bob</code>.  You can add, remove, and
    // change values in the dynamicContentsVars object literal, just call
    // <code>markForRedraw()</code> on the canvas to have the dynamicContents template re-evaluated.
    // <p>
    // Note that <code>this</code> is always evailable inside a dynamic contents string and points to
    // the canvas instance containing the dynamic contents.
    // <p>
    // Used only if +link{attr:Canvas.dynamicContents} : true has been set.
    //
    // @see dynamicContents
    // @visibility external
    //<



    // Per-Canvas CSS overrides.  
	// --------------------------------------------------------------------------------------------
    // Consider defining a style for the individual Canvas instead of using these overrides, since
    // this makes that Canvas skinnable from CSS.

	//>	@attr canvas.margin (number : null : IRW)
    // Set the CSS Margin, in pixels, for this component.  Margin provides blank space outside
    // of the border.
    // <P>
    // This property sets the same thickness of margin on every side.  Differing per-side
    // margins can be set in a CSS style and applied via +link{styleName}.
    // <P>
    // Note that the specified size of the widget will be the size <b>including</b> the margin
    // thickness on each side.
    //
    // @visibility external
	// @group appearance
	//<

	//>	@attr canvas.padding (number : null : IRW)
    // Set the CSS padding of this component, in pixels.  Padding provides space between the
    // border and the component's contents.
    // <P>
    // This property sets the same thickness of padding on every side.  Differing per-side
    // padding can be set in a CSS style and applied via +link{styleName}.
    // <P>
    // Note that CSS padding does not affect the placement of +link{canvas.children}.  To
    // provide a blank area around children, either use +link{canvas.margin,CSS margins} or use
    // a +link{Layout} as the parent instead, and use properties such as
    // +link{layout.layoutMargin} to create blank space.
    //
    // @visibility external
	// @group appearance
	//<

    //>	@attr canvas.border (string : null : IRW)
    // Set the CSS border of this component, as a CSS string including border-width,
    // border-style, and/or color (eg "2px solid blue").
    // <P>
    // This property applies the same border to all four sides of this component.  Different
    // per-side borders can be set in a CSS style and applied via +link{styleName}.
    //
    // @visibility external
	// @group appearance
	//<

    //>	@attr canvas.backgroundColor (string : null : IRW)
	// The background color for this widget. It corresponds to the CSS background-color
    // attribute. You can set this property to an RGB value (e.g. #22AAFF) or a named color
    // (e.g. red) from a list of browser supported color names.
    //
    // @visibility external
	// @group appearance
	//<

	//>	@attr canvas.backgroundImage (SCImgURL : null : [IR])
	// URL for a background image for this widget (corresponding to the CSS "background-image"
    // attribute).
    // @visibility external
	// @group appearance
	//<

    //>	@attr	canvas.backgroundRepeat		(BkgndRepeat : isc.Canvas.REPEAT : [IRW])
    //      Specifies how the background image should be tiled if this widget
    //      is larger than the image. It corresponds to the CSS background-repeat attribute.
    //      See BkgndRepeat type for details.
    // @visibility external
	// @group appearance
    //<
	backgroundRepeat:isc.Canvas.REPEAT,

    //>	@attr	canvas.backgroundPosition		(string : null : [IRW])
    //      Specifies how the background image should be positioned on the widget.
    //      It corresponds to the CSS background-position attribute. If unset,
    //      no background-position attribute is specified if a background image is
    //      specified.
    // @visibility external
	// @group appearance
    //<

    //>	@attr	canvas.mozOutlineOffset (string : "-1px": [IRA])
    // Only applies to Moz Firefox 1.5 and above.
    // When this widget recieves focus, how far should the dotted focus outline appear from
    // the edge of the canvas. A negative value will render the dotted outline inside the
    // canvas 
    // @visibility internal
    //<
    mozOutlineOffset:"-1px",

    //>	@attr	canvas.mozOutlineColor (string : null : [IRA])
    // Only applies to Moz Firefox 1.5 and above.
    // When this widget recieves focus, what color should the dotted focus outline appear.
    // Unspecified by default - gives us the native browser behavior.
    // @visibility internal
    //<    
    //mozOutlineColor:null,
    
    // Skinning
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.appImgDir		(URL : "" : IRWA)
	// Default directory for app-specific images, relative to the Page-wide
    // +link{Page.getAppImgDir(),appImgDir}.
    // @group images
    // @visibility external
	//<
	appImgDir:"",						

    //>	@attr	canvas.skinImgDir		(URL : "images/" : IRWA)
	// Default directory for skin images (those defined by the class), relative to the
    // Page-wide +link{Page.getSkinDir(),skinDir}.
    // @group images
    // @visibility external
	//<
	skinImgDir:"images/",				

	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.cursor		(Cursor : Canvas.DEFAULT : IRWA)
    //      Specifies the cursor image to display when the mouse pointer is
    //      over this widget. It corresponds to the CSS cursor attribute. See Cursor type for
    //      different cursors.
    //  @visibility external
    //  @group  cues
    //  @example dragCreate
    //  @example cursors
    //<
	cursor:isc.Canvas.DEFAULT,
    
    //>	@attr	canvas.disabledCursor       (Cursor : Canvas.DEFAULT : IRWA)
    //      Specifies the cursor image to display when the mouse pointer is
    //      over this widget if this widget is disabled. It corresponds to the CSS cursor 
    //      attribute. See Cursor type for different cursors.
    //  @visibility external
    //  @group  cues
    //<
    disabledCursor:isc.Canvas.DEFAULT,
    
	//>	@attr	canvas.noDropCursor       (Cursor : Canvas.NOT_ALLOWED : IRWA)
    //      Specifies the cursor image to display when the user drags something over this widget
    //      after +link{this.setNoDropIndicator()} has been called.<br>
    //      Default cursor type <code>"not-allowed"</code> is not supported in Safari browsers.
    //      We therefore also provide the alternative +link{canvas.shouldSetNoDropTracker}
    //      no-drop indicator functionality.
    //  @group  cues
    //<
    noDropCursor:isc.Canvas.NOT_ALLOWED,
    
    //>	@attr	canvas.opacity		(number : null : IRWA)
    //      Renders the widget to be partly transparent. A widget's opacity property may
    //      be set to any number between 0 (transparent) to 100 (opaque).
	//		Null means don't specify opacity directly, 100 is fully opaque.
	//		Note that heavy use of opacity may slow down your browser.
	//		See canvas.setOpacity() for details.
    //  @visibility external
    //  @setter setOpacity()
	//  @group	cues
	//<
    
    
    //> @attr canvas.smoothFade (boolean : null : [IRWA])
    // Avoids a visible flash (native browser repaint) for canvases when setting opacity 
    // to / from 100% in  Mozilla browsers.
    // @visibility internal
    //<
    
    
    
    //>Moz
    _useMozOpacity : (isc.Browser.isMoz && isc.Browser.geckoVersion < 20081201),
    //<Moz

    //>	@attr	canvas.overflow		(Overflow : Canvas.VISIBLE : [IRW])
    //			Controls what happens when the drawn size of the content of a Canvas is either
    //			greater or smaller than the specified size of the Canvas.  Similar to the CSS
    //			property overflow, but consistent across browsers.  See Overflow type for
    //			details.
    //  @visibility external
    //  @setter setOverflow()
	//  @group  sizing
	//<
	overflow:isc.Canvas.VISIBLE,			
    
    //>	@attr canvas.alwaysShowVScrollbar (boolean : false : [IRWA])
    // If this canvas has <code>overflow</code> set to <code>"auto"</code>, and is showing 
    // custom scrollbars, settting this property to true will ensure that a custom vertical
    // scrollbar is shown even if the scrollHeight of the widget is less than the specified
    // height
    //  @visibility internal
	//  @group  sizing
	//<
    
	alwaysShowVScrollbar:false,

    // Scrolling
	// --------------------------------------------------------------------------------------------

    

	//>	@attr	canvas.showCustomScrollbars		(boolean : true : IRWA)
    // Whether to use the browser's native scrollbars or SmartClient-based scrollbars.
    // <P>
    // SmartClient-based scrollbars are skinnable, giving you complete control over look and
    // feel.  SmartClient-based scrollbars also enable some interactions not possible with
    // native scrollbars, such as +link{ListGrid.fixedRecordHeights,variable height records}
    // in grids in combination with +link{listGrid.dataPageSize,data paging}.
    // <P>
    // Native browser scrollbars are slightly faster simply because there are less SmartClient
    // components that need to be created, drawn and updated.  Each visible SmartClient-based
    // scrollbar on the screen has roughly the impact of two StretchImgButtons.
    // <P>
    // SmartClient is always aware of the size of the scrollbar, regardless of whether native
    // or custom scrollbars are used, and regardless of what operating system and/or operating
    // system "theme" or "skin" is in use.  This means SmartClient will correctly report the
    // +link{canvas.getViewportHeight(),viewport size}, that is, the interior area of the
    // widget excluding space taken by scrollbars, which is key for exactly filling a component
    // with content without creating unnecessary scrolling.
    // <P>
    // The <code>showCustomScrollbars</code> setting is typically overridden in load_skin.js in
    // order to change the default for all SmartClient components at once, like so:
    // <pre>
    //     isc.Canvas.addProperties({ showCustomScrollbars:false });
    // </pre>
    //
	// @group scrolling
    // @visibility external
	//<
    showCustomScrollbars : 
    // NOTE: leading logical NOT, so the rest of this conditional specifies the conditions in which
    // we use native scrollbars
    !(
        // use native scrollbars on IE5+
        (isc.Browser.isOpera || isc.Browser.isIE && isc.Browser.version > 4) ||
        
        (isc.Browser.isUnix && isc.Browser.isMoz && isc.Browser.geckoVersion >= 20020826 
                                                 && isc.Browser.geckoVersion <= 20031007)

    ),

    //>	@attr	canvas.scrollbarSize		(number : 16 : IRWA)
	//			How thick should we make the scrollbars for this canvas.<br>
	//			NOTE: has no effect if showCustomScrollbars is false. 
	//		@group	scrolling
    //      @visibility external
    //      @see getScrollbarSize()
	//<
	scrollbarSize:16,

	// NOTE: the following properties only apply when showCustomScrollbars is true

    //>	@attr canvas.scrollbarConstructor   (Class : "Scrollbar" : [IA])
	// The class that will be used to create custom scrollbars for this component. Set this
	// attribute to a Scrollbar subclass with e.g. a different skinImgDir, to customize scrollbar
	// appearance for this component only.
	// @group	scrolling
	//<
	scrollbarConstructor:"Scrollbar",

    //>	@attr	canvas.scrollLeft		(number : 0 : IRWA)
	//			number of pixels that this canvas is shifted leftwards due to scrolling.
	//		@group	scrolling
	//<
	scrollLeft:0,

	//>	@attr	canvas.scrollTop		(number : 0 : IRWA)
	//			number of pixels that this canvas is shifted upwards due to scrolling.
	//		@group	scrolling
	//<
	scrollTop:0,

    //>     @attr   canvas.scrollDelta (number : 20 : RWA)
    // Amount to scroll when the scroll button is pressed
    //              @group  scrolling
    //<
    scrollDelta:20,

    // Disabling
	// --------------------------------------------------------------------------------------------
    
    //>	@attr   canvas.disabled		(boolean : false : IRWA)
    //      If set to true, the widget will be disabled. A widget is only considered enabled 
    //      if it is individually enabled and all parents above it in the containment hierarchy 
    //      are enabled. This allows you to enable or disable all components of a complex 
    //      nested widget by enabling or disabling the top-level parent only.
    //  @getter isDisabled
    //  @setter enable, disable
    //  @visibility external
    //  @group enable
    //<
    //disabled:false,
    
    //> @attr   canvas.enabled  (boolean : "unset" : IRWA)
    // If set to true, this widget will be enabled, if set to false, or null, this 
    // widget will be disabled.
    // @visibility external
    // @group enable
    // @deprecated As of SmartClient version 5.5 this property has been deprecated. The
    //   +link{canvas.disabled} property will be used to govern enabled/disabled state instead 
    //  if <code>this.enabled</code> has not been changed from its default value.
    //<
    
    _$unset:"unset",
    enabled:"unset",

    //>	@attr	canvas.redrawOnDisable		(boolean : false : IRWA)
	//			do we redraw when the disabled state changes ?
	//		@group	drawing, enable
	//<
	redrawOnDisable:false,
    
    //> @attr  canvas.redrawOnEnable       (boolean : false : IRWA)
    // do we redraw when the enabled state changes ?
    // @group  drawing, enable
    // @deprecated As of SmartClient 5.5 use +link{canvas.redrawOnDisable} instead
    //<    

    // Peers: for which actions should we mimic what the master does?
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas._redrawWithMaster		(boolean : true : IRWA)
	//		For a peer, should we redraw automatically when our masterElement is redrawn?
	//		@group	drawing, containment
	//<
	_redrawWithMaster:true,	

	//>	@attr	canvas._resizeWithMaster		(boolean : true : IRWA)
	//		For a peer, should we resize automatically when our masterElement is resized?
	//		@group	drawing, containment
	//<
    _resizeWithMaster:true,

	//>	@attr	canvas._moveWithMaster		(boolean : true : IRWA)
	//		For a peer, should we move automatically when our masterElement moves?
	//		@group	drawing, containment
	//<
    _moveWithMaster:true,
    
    //> @attr   canvas._setOpacityWithMaster    (boolean : true : IRWA)
    // For a peer, should our opacity be automatically updated to match that of our 
    // masterElement that changes?
    // @group drawing, containment
    //<
    _setOpacityWithMaster:true,


	//>	@attr	canvas.redrawOnResize		(boolean : true : IRWA)
	// Should this element be redrawn in response to a resize?
    // <P>
    // Should be set to true for components whose +link{getInnerHTML,inner HTML} will not
    // automatically reflow to fit the component's new size.
    //
	// @group drawing
	// @visibility external
	//<


    //>	@attr	canvas._showWithMaster (boolean : true : IRWA)
	//		For a peer, should we be shown automatically when our master is shown?
	//		@group	drawing, containment
	//<
	_showWithMaster:true,	
    
 
	// --------------------------------------------------------------------------------------------
                                           
    //>	@attr	canvas._redrawWithParent		(boolean : true : IRWA)
	//		Should we redraw automatically when our parentElement is redrawn?
	//		Turn this off ONLY if you're completely committed to redrawing an element
	//		 manually yourself.
	//		@group	drawing, containment
	//<
	_redrawWithParent:true,

    // Focus
	// --------------------------------------------------------------------------------------------
    
    //>	@attr	canvas.hasFocus		(boolean : false : IRWA)
	// Do we have the focus?
	//		@group	focus
	//<

    //>	@attr	canvas.canFocus		(boolean : null : IRWA)
    // Can this widget be allowed to become the target of keyboard events?
    // <P>
    // If canFocus is unset (the default), only scrollable widgets with visible scrollbars are
    // focusable, to allow for keyboard scrolling.
    // <P>
    // A widget normally receives focus by being clicked on or tabbed to.
    //
    //		@group	focus, events
    //      @setter setCanFocus()
    // @visibility external
    // @example focus
	//<
    
    //> @attr   canvas.showeFocusOutline    (boolean : true : IRWA)
    // For focusable widgets, should the native dotted focus outline be shown, where supported?
    // @visibility internal
    //<
    showFocusOutline:true,

    //>	@attr	canvas.redrawOnFocus		(boolean : false : IRWA)
    //			should we redraw automatically when this object accepts the focus?
    //		@group	drawing, focus
    //<
    
    //> @attr   canvas.tabIndex (number : null : IRWA)
    // If specified this governs the tabIndex of the widget in the page's tab order.
    // Note that by default SmartClient auto-assigns tab-indices, ensuring focusable widgets
    // are reachable by tabbing in the order in which they are drawn on the page.
    // <code>canvas.tabIndex</code> cannot be set to greater than 
    // +link{classAttr:Canvas.TAB_INDEX_FLOOR} - as we reserve the values above this range for
    // auto-assigned tab-indices.
    // @group focus
    // @visibility external
    //<
    // Some comments on manual assignment of tabIndex:
    // - useful for inserting into native tab order:
    //   - setting tabIndex to 0 to allow an ISC widget to be inserted into the native, automatic
    //     tab order of a series of native elements which surround it and which have no tabIndex
    //     assigned (where the ISC widget would be drawn either relpos or via Canvas.drawContext)
    //   - setting explicit tabIndex to allow an ISC widget to be inserted into a series of
    //     native elements with explicit tab indices
    //   - NOTE: with both of the above use cases, if a compound widget is inserted, all
    //     focuseable children will need an explicit tabIndex.  In some cases this works
    //     automatically, eg, in a ListGrid, the body and header recieve the same tabIndex by
    //     default
    //  - Cannot be used to slot a widget into the middle of the ISC auto-assigned tab loop,
    //    as we enforce the TAB_INDEX_FLOOR upper limit on manually assigne tabindices
    
    
    _useNativeTabIndex:(isc.Browser.isIE && isc.Browser.version >= 5) || 
                        (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111),
    
    
    _useFocusProxy:(isc.Browser.isMoz && isc.Browser.geckoVersion < 20051111)
                   || isc.Browser.isSafari || isc.Browser.isOpera,
    

    //> @attr   canvas.accessKey (string : null : IRWA)
    // If specified this governs the accessKey for the widget.
    // This should be set to a character - when a user hits Alt+[accessKey], or in Mozilla
    // Firefox 2.0 and above, Shift+Alt+[accessKey], focus will be given to the widget in
    // question.
    // @group focus
    // @visibility external
    //<

    // Context Menu
	// --------------------------------------------------------------------------------------------

	//>	@attr	canvas.contextMenu		(Menu : null : IRWA)
	// Context menu to show for this object, an instance of the Menu widget.
	//		@group	cues
    //  @see canvas.showContextMenu()
    // @visibility external
    // @example contextMenus
	//<

	//>	@attr	canvas.contextMenuProperties		(object : object : IRW)
    // Default properties for automatically generated context menus
    //<
    
	contextMenuProperties:{
		autoDraw:false,					
		width:200,						
		showIcons:true
	},
	
    //>CornerClips
	// ----------------------------------------------------------------------------------------- 

    //> @attr canvas.clipCorners    (boolean : false : [IR])
    // Whether to clip corners
    // @visibility cornerClips
    //<
    //clipCorners:false,

    //> @attr canvas.clippedCorners   (Array : ["TL", "TR", "BL", "BR"] : [IR])
    // List of corners that should be clipped
    // @visibility cornerClips
    //<
	clippedCorners:["TL","TR","BL","BR"],

    //> @attr canvas.noCornerClipImages  (boolean : false : [IR])
    // For development purposes, create corner clips without requiring images.  Only works for
    // corners where width and height are equal.
    // <P>
    // Highly experimental and IE specific.
    //
    // @visibility cornerClips
    //<
	//noCornerClipImages:false,

    //> @attr canvas.cornerClipColor     (cssColor : "FFFFFF" : [IR])
    // HEX color code (WITHOUT #) to match the background.
    //
    // @visibility cornerClips
    //<
	cornerClipColor:"FFFFFF", 

    //> @attr canvas.cornerClipImage   (SCImgURL : "[SKIN]roundcorner.gif" : [IR])
    // Base name of image to use for corner clipping images.
    // <P>
    // The full name of each corner image is (base + color + corner name), eg,
    // "roundcorner_FFFFFF_TL.gif"
    //
    // @visibility cornerClips
    //<
	cornerClipImage:"[SKIN]corner.gif", 

    //> @attr canvas.cornerClipSize        (number of pixels : 10 : [IR])
    // Size in pixels for corner clips
    // @visibility cornerClips
    //<
	cornerClipSize:10,

    //> @attr canvas.cornerClipWidth       (number of pixels : null : [IR])
    // Width in pixels for corner clips.  Defaults to cornerClipSize when unset.
    // @visibility cornerClips
    //<

    //> @attr canvas.cornerClipHeight      (number of pixels : null : [IR])
    // Height in pixels for corner clips.  Defaults to cornerClipSize when unset.
    // @visibility cornerClips
    //<

	//_cornerClips:null,	// refs to the generated corner cap elements
	
	_cornerProperties:{
		_generated:true,
		overflow:"hidden",
        
		_redrawWithMaster:false,
		_resizeWithMaster:false,
		autoDraw:false,
		skinImgDir:"images/corners/",
		
		// scroll cornercap contents to appropriate position after drawing
		// (should only apply to no-image corners)
		draw : function () {
			this.Super("draw",arguments);
            
		}
	},
    
    

    //<CornerClips

	// --------------------------------------------------------------------------------------------
	//>	@attr	canvas.prompt		(string: null : IRW)
	// Prompt displayed in hover canvas if +link{canvas.showHover,showHover} is true. 
    // @visibility external
	// @group	hovers
    // @example customHovers
	//<

	// Drag and Drop
	// --------------------------------------------------------------------------------------------

    //>	@attr	canvas.canDrag		(boolean : false : IRWA)
    //      Indicates whether this widget can initiate custom drag-and-drop operations (other than
    //      reposition or resize). Normally canDragReposition or canDragResize would be used
    //      instead of this property.
    //      Note: this property may be manipulated by higher-level dragging semantics.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragCreate
    //<

    dragOutlineStyle:"dragOutline",
    
    //>	@attr	canvas.dragStartDistance		(number : 5 : IRWA)
    //
	// Number of pixels the cursor needs to move before the EventHandler starts a drag operation.
    //
	// @group dragdrop
    // @visibility external
	//<
	dragStartDistance:5,
        
    //>DragScrolling
    //>	@attr	Canvas.canDragScroll (boolean : true : IRWA)
    //      If this Canvas is canAcceptDrop:true, when the user drags a droppable widget over
    //      an edge of the widget, should we scroll to show the rest of the widget's content?
    //      Returned from canvas.shouldDragScroll().
    //      @visibility external
    //      @see    shouldDragScroll()
    //      @group  dragging
    //<
    canDragScroll : true,
        
    //>	@attr	canvas.dragScrollDelay    (number : 100 : IRWA)
    //      If this widget supports drag-scrolling, This property specifies how many ms the
    //      user must hover over the drag-scroll threshold before scrolling begins.
    //      @visibility external
    //      @group  dragging
    //<
    dragScrollDelay:100,
    
    
    //>	@attr	canvas.dragScrollThreshold   (measure : "10%" : IRWA)
    //      If this widget allows drag-scrolling, the dragScrollThreshold is the distance from
    //      the edge of the widget viewport that the user must drag-hover to be in the 
    //      scrolling area.  This can be specified as a percentage value like "10%" or an 
    //      absolute pixel value.
    //      @visibility internal
    //      @group  dragging
    //<
    dragScrollThreshold:"10%",
    
    //>	@attr	canvas.minDragScrollIncrement (measure : 1 : IRWA)
    //      If this widget allows drag-scrolling, the rate at which the widget will be scrolled
    //      while the user drag-hovers close to the edge of the widget is determined by how
    //      far the mouse pointer is from the edge.
    //      We provide 2 properties to control this:<br>
    //      - minDragScrollIncrement denotes what size increments will be used to scroll the 
    //        widget while the pointer is exactly 1*this.dragScrollThreshold from the edge of
    //        the widget<br>
    //      - maxDragScrollIncrement denotes what size increments will be used to scroll the 
    //        widget while the pointer is exactly over the edge of the widget<br>
    //      Each of these properties can be specified as an absolute pixel value to scroll, or
    //      a percentage of the scrollSize of the widget.
    //      @visibility internal
    //      @group  dragging
    //<
    minDragScrollIncrement:1,

    //>	@attr	canvas.maxDragScrollIncrement (measure : "5%" : IRWA)
    //      If this widget allows drag-scrolling, the rate at which the widget will be scrolled
    //      while the user drag-hovers close to the edge of the widget is determined by how
    //      far the mouse pointer is from the edge.
    //      We provide 2 properties to control this:<br>
    //      - minDragScrollIncrement denotes what size increments will be used to scroll the 
    //        widget while the pointer is exactly 1*this.dragScrollThreshold from the edge of
    //        the widget<br>
    //      - maxDragScrollIncrement denotes what size increments will be used to scroll the 
    //        widget while the pointer is exactly over the edge of the widget<br>
    //      Each of these properties can be specified as an absolute pixel value to scroll, or
    //      a percentage of the scrollSize of the widget.
    //      @visibility internal
    //      @group  dragging
    //<    
    maxDragScrollIncrement:"5%",
    //<DragScrolling

    
    //>	@attr canvas.dragIntersectStyle (DragIntersectStyle : "mouse" : IRWA)
    // This indicates how the system will test for droppable targets: either by intersection
    // with the mouse or intersection with the rectangle of the dragMoveTarget.
    // @group dragdrop
    // @visibility external
    //<
	dragIntersectStyle : isc.EventHandler.INTERSECT_WITH_MOUSE,

    //>	@attr	canvas.canDragReposition		(boolean : false : IRWA)
    //      Indicates whether this widget can be moved by a user of your application by simply
    //      dragging with the mouse.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragEffects
    //<

    //>	@attr	canvas.dragRepositionCursor		(Cursor : isc.Canvas.MOVE : IRWA)
    // Cursor to switch to if the mouse is over a widget that is drag repositionable.
    //  @visibility external
    //  @group  dragdrop
    //<
	dragRepositionCursor:isc.Canvas.MOVE,

    //>	@attr	canvas.canDragResize		(boolean : false : IRWA)
    //      Indicates whether this widget can be resized by dragging on the edges and/or corners of
    //      the widget with the mouse.
    //  @visibility external
    //  @group  dragdrop
    //  @example dragResize
    //<
				
	//>	@attr	canvas.resizeFrom		(array : null : IRWA)
    //      Allows resizing in certain edges or corners. The default value of null indicates that
    //      the widget is resizable from any corner or edge. To restrict resizing to only certain
    //      corners, set resizeFrom to an array of any of the values listed:<br>
    //      T      top edge<br>
    //      B      bottom edge<br>
    //      L      left edge<br>
    //      R      right edge<br>
    //      TL     top-left corner<br>
    //      TR     top-right corner<br>
    //      BL     bottom-left corner<br>
    //      BR     bottom-right corner<br>
	//	    E.g. setting this property to a value of ["R","TR","BR"] would restrict resizing to
    //      the right edge, top-right corner and bottom-right corner only
    //  @visibility external
	//  @group	dragdrop
    //  @example dragResize
	//<

    //>	@attr	canvas.dragScrollType		(string : "any" : IRWA)
    //      If this canvas is a dragTarget and this property is set to "parentsOnly", then only its
    //      parent chain should be checked for possible scrollers.
    //  @group  dragdrop
    //<
    dragScrollType:"any",
       
    //>	@attr	canvas.dragScrollDirection (string : null : IRWA)
    // If this canvas is a dragTarget, this property may be set to limit which direction the
    // parent / other scrollable widget is scrolled on drag-over.<br>
    // Options are "vertical" or "horizontal".
    //  @group  dragdrop
    //<
    //dragScrollDirection:null,
    
    //> @attr canvas.canHover (boolean : null : IRW)
    // Will this Canvas fire hover events when the user hovers over it, or one of its children?
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @see canvas.hover()
    //<
    //canHover:null,
   
    //> @attr canvas.hoverDelay (number : 300 : IRW)
    // If <code>this.canHover</code> is true, how long should the mouse be kept over this
    // widget before the hover event is fired
    //  @group hovers
    //  @visibility external
    //  @see canvas.canHover
    //  @see canvas.hover()
    //<
    hoverDelay:300,
    
    //> @attr canvas.showHover (boolean : true : IRW)
    // If <code>this.canHover</code> is true, should we show the global hover canvas by default
    // when the user hovers over this canvas?
    //  @group hovers
    //  @visibility external
    //  @see canvas.getHoverHTML()
    //<
    showHover:true,
    
    //> @attr canvas.hoverWidth (measure : null : IRW)
    // If +link{canvas.showHover,this.showHover} is true, this property can be used to customize the
    // width of the hover canvas shown.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @example customHovers
    //<
    //hoverWidth:null,
    
    //> @attr canvas.hoverHeight (measure : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // height of the hover canvas shown.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverHeight:null,
    
    //> @attr canvas.hoverAlign (Alignment : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // alignment of content in the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverAlign:null,
    
    //> @attr canvas.hoverVAlign (VerticalAlignment : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // vertical alignment of content in the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverVAlign:null,
    
    //> @attr canvas.hoverWrap (boolean : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to customize the
    // whether content in the hover canvas is displayed in a single line, or wraps.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //<
    //hoverWrap:null,
    
    //> @attr canvas.hoverStyle (CSSStyleName : null : IRW)
    // If <code>this.showHover</code> is true, this property can be used to specify the
    // css style to apply to the hover canvas.
    //  @group hovers
    //  @visibility external
    //  @see canvas.showHover
    //  @example customHovers
    //<
    //hoverStyle:null,    
    
    //> @attr canvas.hoverOpacity (number : null : IRW)
    // If <code>this.showHover</code> is true, should the hover canvas be shown with opacity
    // other than 100?
    // @visibility external
    // @group hovers
    // @see canvas.showHover
    //  @example customHovers
    //<
    //hoverOpacity:null,
    
    //> @attr canvas.hoverMoveWithMouse (boolean : null : IRW)
    // If <code>this.showHover</code> is true, should this widget's hover canvas be moved with
    // the mouse while visible?
    // @visibility external
    // @group hovers
    // @see canvas.showHover
    //<
    
    //>	@attr	canvas.edgeMarginSize		(number : 5 : IRWA)
	// How far into the edge of an object do we consider the "edge" for drag resize purposes?
	//		@group	dragdrop
    //  @example dragResize
	//<
	edgeMarginSize:5,					

	//>	@attr	canvas.edgeCursorMap		(object : {...} : IRWA)
	// Cursor to use when over each edge of a Canvas that is drag resizable.
    // <P>
    // To disable drag resize cursors, set the edgeCursorMap property to null.
    //
    //  @see resizeFrom
    //  @visibility external
	//  @group	dragdrop
	//<
    // NOTE: cursor change is actually accomplished in isc.EventHandler.handleMouseMove()
	edgeCursorMap : {
			"T":"n-resize",
			"L":"w-resize",
			"B":"s-resize",
			"R":"e-resize",
			"TL":"nw-resize",
			"TR":"ne-resize",
			"BL":"sw-resize",
			"BR":"se-resize"
	},

	//>	@attr	canvas.keepInParentRect		(boolean or rect: null : IRWA)
	// Constrains drag-resizing and drag-repositioning of this canvas to either the rect of its
    // parent (if set to true) or an arbitrary rect (if set to a [Left,Top,Width,Height] rect
    // array).  If this canvas has no parent, constrains dragging to within the browser window.
    // <p>
	// Affects target and outline dragAppearance, not tracker.
    // <p>
    // Note: keepInParentRect affects only user drag interactions, not programmatic moves.
    //
    // @group dragdrop
    // @visibility external
    // @example dragCreate
	//<

	//>	@attr	canvas.dragAppearance		(DragAppearance : isc.EventHandler.OUTLINE : IRWA)
    //      Visual appearance to show when the object is being dragged.
    //  @visibility external
	//  @group	dragdrop
	//<
	dragAppearance:isc.EventHandler.OUTLINE,

    //>	@attr	canvas.dragType		(string : null : IRWA)
    //      The "type" of thing given as a string that can be dragged from this widget. If
    //      specified, this will be matched up with the dropTypes of droppable widgets as detailed
    //      in the dropTypes property.
    //  @visibility external
	//  @group	dragdrop
    // @see canvas.dropTypes
	//<

	//>	@attr	canvas.dragTarget		(Canvas | String: null : IRWA)
    //      A different widget that should be actually dragged when dragging initiates on this
    //      widget. One example of this is to have a child widget that drags its parent, as with a
    //      drag box. Because the parent automatically repositions its children, setting the drag
    //      target of the child to the parent and then dragging the child will result in both
    //      widgets being moved.<br>
    //      Valid dragTarget values are:<br>
    //      - <code>null</code> (default) [this widget is its own drag target]<br>
    //      - Pointer to another widget, or widget ID<br>
    //      - <code>"parent"</code> drag target is this widget's 
    //         +link{Canvas.parentElement, parentElement}<br>
    //      - <code>"top"</code> drag target is this widget's 
    //         +link{Canvas.topElement, topElement}<br>
    //  @see EventHandler.getDragTarget()
    //  @visibility external
	//  @group	dragdrop
	//<
    
	//>	@attr canvas.showDragShadow   (boolean : null : IRWA)
    // When this widget is dragged, if its dragAppearance is <code>"target"</code>, should
    // we show a shadow behind the canvas during the drag.
    //
	// @group dragdrop
    // @visibility external
    // @example dragEffects
	//<
    
	//>	@attr canvas.dragOpacity  (number : null : IRWA)
    // If this widget has dragAppearance <code>"target"</code>, this value specifies the
    // opacity to render the target while it is being dragged. A null value implies we do
    // not modify the opacity.
    //
	// @group dragdrop
    // @visibility external
    // @example dragEffects
	//<
    
    //>	@attr	canvas.canDrop		(boolean : false : IRWA)
    //      Indicates that this object can be dropped on top of other widgets. Only valid if 
    //      canDrag or canDragReposition is true.
	//  @group	dragdrop
    //  @visibility external
    //  @example dragCreate
	//<    
    
    //>	@attr	canvas.canAcceptDrop		(boolean : false : IRWA)
    //      Indicates that this object can receive dropped widgets (i.e. other widgets can be
    //      dropped on top of it).
	//  @group	dragdrop
    //  @visibility external
    //  @example dragCreate
	//<


    //>	@attr	canvas.canDropBefore		(boolean : null : IRWA)
    // When explicitly set to false, disallows drop before this member in the Layout.
    //
	// @group	layoutMember
    // @see Layout
    // @visibility external
	//<    

    //>	@attr	canvas.dropTypes		(string | array of strings : isc.Canvas.ANYTHING : IRWA)
    //
    // The "type" of thing(s) that can be dropped on this widget specified as a string or an
    // array of strings (indicating multiple types). Leave this with the value null to indicate
    // that this widget can accept anything dropped on it from the page.
    //
	// @group dragdrop
    //
    // @see Canvas.willAcceptDrop()
    // @see Canvas.dragType
    //
    // @visibility external
	//<
	dropTypes:isc.Canvas.ANYTHING,
    
	//>	@attr	canvas.mouseStillDownInitialDelay		(number : 400 : IRWA)
	// Amount of time (in msec) before mouseStillDown events start to be fired for this object. 
    //
	// @group	events
    // @visibility external
	//<
    
	mouseStillDownInitialDelay:400,

	//>	@attr	canvas.mouseStillDownDelay		(number : 100 : IRWA)
	// Amount of time (in msec) between 'mouseStillDown' events for this object
    //
    // @visibility external
	// @group events
	//<
	mouseStillDownDelay:100,

	//>	@attr	canvas.doubleClickDelay		(number : 250 : IRWA)
	// Amount of time (in msec) between which two clicks are considered a single click
	// @group	events
    // @visibility external
	//<
	doubleClickDelay:250,
    
    //> @attr   canvas.noDoubleClicks   (boolean : null : IRWA)
    // If true, this canvas will recieve all mouse-clicks as single click events rather than
    // doubleClicks.
    // @group events
    // @visibility external
    //<
    //noDoubleClicks:false,
    
	// --------------------------------------------------------------------------------------------
    // variable name to use with refreshFromServer / replaceFromServer
    refreshVariable : "refresh",
     
	// --------------------------------------------------------------------------------------------

    
    //>Moz
    _useMozScrollbarsNone : (isc.Browser.isMoz &&
                               (!isc.Browser.isUnix || isc.Browser.geckoVersion > 20031007)),
    //<Moz

    
    //_canScrollHidden : isc.Browser.isMoz && isc.Browser.geckoVersion >= 20041107,

    // Whether to create a clipDiv/contentDiv pair vs a single DIV.
    // NOTE: we have a function shouldCreateClipDiv() which tells us under what circumstances we
    // really need to use a clipDiv, however, we currently always use a clipDiv because we can't
    // switch strategies without calling clear() and then draw() to rewrite our DIV[s].

    // Writing two DIVs has a performance impact, so specific subclasses which don't rely on all of
    // Canvas' features working correctly might want to override this in order to have a lighter
    // weight DOM representation.  NOTE: the strategy of using nested DIVs can actually be used in
    // any browser, in fact, it works in IE and corrects some bugs, although it is noticeably slower
    useClipDiv : (isc.Browser.isMoz || isc.Browser.isSafari || isc.Browser.isOpera),
    //useClipDiv : false,
 
    
    manageChildOverflow:true,
   
    

    //> @attr canvas.useEventParts  (boolean : false : IRWA)
    // If true, when this widget recieves events, it will check whether the native DOM element
    // that recieved the event has been marked as a special "part" of this widget, and if so
    // fire the appropriate part events.<br>
    // Elements written into this canvas can be marked as 'parts' by setting the 'eventpart'
    // attribute to the name of the part type. The events fired are then based upon this 
    // property. For example an element with eventpart set to "rect" would cause this widget
    // to fire "rectMouseOver", "rectMouseOut" handlers. The element, and a unique ID for the
    // part would be passed into those custom handlers
    // @visibility eventParts
    //<
    
    
    // _lastOverPart - if we're handling partwise events, this is used to track what the user
    // was last over within this canvas.
    // Format: {part:[parttype], element:[element], ID:[ID]}
    _lastOverPart:{},

    //> @type PercentBoxModel
    // Determines sizing model when sizing / positioning a canvas relative to its
    // +link{canvas.percentBox,percentBox}. 
    // @value "visible" use coordinates relative to the
    // {+link{canvas.getVisibleHeight()},visibleHeight} and width of the other canvas
    // @value "viewport" use coordinates relative to the
    // {+link{canvas.getViewportHeight()},viewportHeight} and width of the other canvas
    // @visibility external
    //<
   
    //> @attr canvas.percentSource (Canvas : null : IRWA) 
    // If this canvas has its size specified as a percentage, this property allows the user to
    // explicitly designate another canvas upon which sizinng will be based.
    // <P>
    // If unset percentage sizing is based on<br>
    // - the +link{canvas.masterElement,masterElement} if there is one and
    //   +link{canvas.snapTo,snapTo} is set,<br> 
    // - otherwise on the amount of space available in this widget's parentElement, if this is
    //   a child of some other widget<br>
    // - otherwise the page size.
    // @group sizing
    // @see attr:canvas.percentBox
    // @visibility external
    //<
    
    //> @attr canvas.percentBox (PercentBoxModel : "visible" : IRA)
    // Governs the model to be used when sizing canvases with percentage width or height, or 
    // positioning widgets with a specified +link{canvas.snapTo,snapTo}.
    // <P>
    // Only affects widgets with a a specified +link{canvas.percentSource,percentSource}, or 
    // widgets that have +link{canvas.snapTo} set and are peers of some 
    // +link{canvas.masterElement,other canvas}.
    // <P>
    // Determines whether the coordinates used for sizing (for percentage sized widgets) and
    // positioning (if <code>snapTo</code> is set) should be relative to the visible size or the
    // viewport size of the percentSource or masterElement widget.
    // @group sizing
    // @visibility external
    //<
    percentBox:"visible",
    _$viewport:"viewport",
    
	//> @attr canvas.snapTo (String : null : IRW)
	// Position this widget such that it is aligned with ("snapped to") an edge of its 
    // +link{canvas.masterElement,masterElement} (if specified), or its
    // +link{canvas.parentElement,parentElement}.
    // <P>
    // Note that this property also impacts the sizing of this widget. If this widgets size
    // is specifed as a percent value, and has no explicit
    // +link{Canvas.percentSource}, sizing will be calculated based on the size of the 
    // masterElement when snapTo is set.
    // <P>
	// Possible values: BR, BL, TR, TL, R, L, B, T, C where B=Bottom, T=Top, L=Left, R=right
    // and C=center
    //
    // @group sizing
    // @see canvas.snapEdge
    // @see canvas.percentBox
	// @visibility external
	//<
    
	//> @attr canvas.snapEdge (String : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined to this widget, this property can be used to
    // define which edge of this widget should be snapped to an edge of the master or parent 
    // element.
    // <P>
    // If unspecified the, default snapTo behavior is set up to align the "snapTo" edge of this 
    // widget with the snapTo edge of the master or parent.
    //
    // @group sizing
    // @see canvas.snapTo
	// @visibility external
	//<
    
    //> @attr canvas.snapOffsetLeft (integer : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined for this widget, this property can be used to
    // specify an offset in px or percentage for the left coordinate of this widget.
    // <P>
    // For example if <code>snapTo</code> is specifed as <code>"L"</code> and 
    // <code>snapOffsetLeft</code> is set to 6, this widget will be rendered 6px inside the left
    // edge of its parent or master element.
    // @group sizing
    // @see canvas.snapTo
    // @visibility external
    //<
	
    //> @attr canvas.snapOffsetTop (integer : null : IRW)
    // If +link{canvas.snapTo,snapTo} is defined for this widget, this property can be used to
    // specify an offset in px or percentage for the top coordinate of this widget.
    // <P>
    // For example if <code>snapTo</code> is specifed as <code>"T"</code> and 
    // <code>snapOffsetTop</code> is set to 6, this widget will be rendered 6px below the top
    // edge of its parent or master element.
    // @group sizing
    // @see canvas.snapTo
    // @visibility external
    //<
    
    //> @attr canvas.snapToGrid (boolean : null : IRW)
	// Causes this canvas to snap to its parent's grid when dragging.
	// @visibility external
	// @see	Canvas.childrenSnapToGrid
	// @group dragdrop
	//<
	
	//> @attr canvas.snapResizeToGrid (boolean : null : IRW)
	// Causes this canvas to snap to its parent's grid when resizing.
	// Note that this value defaults to the Canvas's 
	// +link{Canvas.snapToGrid, snapToGrid} value if undefined.
	// @visibility external
	// @group dragdrop
	//<
	
	//> @attr canvas.childrenSnapToGrid (boolean : null : IRW)
	// If true, causes this canvas's children to snap to its grid when dragging.
	// This behavior can be overridden on a per-child basis by setting the 
	// +link{Canvas.snapToGrid, snapToGrid} value on the child.
	// @visibility external
	// @group dragdrop
	//<
	
	//> @attr canvas.childrenSnapResizeToGrid (boolean : null : IRW)
	// If true, causes this canvas's children to snap to its grid when resizing.
	// This behavior can be overridden on a per-child basis by setting the 
	// +link{Canvas.snapToGrid, snapToGrid} or 
	// +link{Canvas.snapResizeToGrid, snapResizeToGrid} value on the child.
	// @visibility external
	// @group dragdrop
	//<
	
	//> @attr canvas.snapHGap (number : 20 : IRW)
	// The horizontal grid size to use, in pixels, when snap-to-grid is enabled.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapHGap: 20,	
	
	//> @attr canvas.snapVGap (number : 20 : IRW)
	// The vertical grid size to use, in pixels, when snap-to-grid is enabled.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapVGap: 20,
	
	//> @attr canvas.snapHDirection (string : Canvas.AFTER : IRW)
	// The horizontal snap direction.
	// Set this value to Canvas.BEFORE to snap to the nearest gridpoint to the left;
	// set it to Canvas.AFTER to snap to the nearest gridpoint to the right; and set
	// it to Canvas.NEAREST to snap to the nearest gridpoint in either direction.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapHDirection: isc.Canvas.AFTER,
	
	//> @attr canvas.snapVDirection (string : Canvas.AFTER : IRW)
	// The vertical snap direction.
	// Set this value to Canvas.BEFORE to snap to the nearest gridpoint above;
	// set it to Canvas.AFTER to snap to the nearest gridpoint below; and set
	// it to Canvas.NEAREST to snap to the nearest gridpoint in either direction.
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapVDirection: isc.Canvas.AFTER,
	
	//> @attr canvas.snapAxis (string : Canvas.BOTH : IRW)
	// Describes which axes to apply snap-to-grid to.
	// Valid values are Canvas.HORIZONTAL, Canvas.VERTICAL and Canvas.BOTH
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.snapResizeToGrid
	// @see	Canvas.childrenSnapToGrid
	// @see	Canvas.childrenSnapResizeToGrid
	//<
   snapAxis: isc.Canvas.BOTH,
	
	//> @attr canvas.snapOnDrop (boolean : true : IRWA)
	// When this canvas is dropped onto an object supporting snap-to-grid, should it snap
	// to the grid (true, the default) or just drop wherever the mouse is (false).
	// @visibility external
	// @group dragdrop
	// @see	Canvas.snapToGrid
	// @see	Canvas.shouldSnapOnDrop()
	//<
   snapOnDrop: true
	
});


isc.Canvas.addMethods({

// basic terms
_$resize: "resize",
_$draw: "draw",
_$hidden: "hidden",
_$redraw: "redraw",
_$undefined: "undefined",

// various log and stat categories
_$draws: "draws",
_$drawing: "drawing",
_$redraws: "redraws",
_$autoDraw: "autoDraw",

// insertAdjacentHTML positions

_$beforeBegin : "beforeBegin",
_$afterBegin: "afterBegin",
_$beforeEnd: "beforeEnd",
_$afterEnd: "afterEnd",

// characters
_$rightAngle : ">",
_$singleQuote : "'",

// Initialization
// --------------------------------------------------------------------------------------------

//>	@method	canvas.init()	(A)
//
// This method performs some basic initialization common to all UI components.  To do custom UI
// component initialization, you should generally override +link{Canvas.initWidget()}.  This
// method does the following, in order:
// <ul>
// <li>Sets up a global reference to this instance as described in +link{Canvas.ID}.
// <li>Ensures certain numeric properties have numeric values (e.g. width, height, padding,
// margin)
// <li>Ensures +link{canvas.children} and +link{canvas.peers} are Arrays.
// <li>Calls +link{Canvas.initWidget()}
// <li>Creates +link{showEdges,edges} and +link{showShadow,shadow}, if so configured.
// <li>Calls +link{Canvas.draw()} if +link{Canvas.autoDraw} is set on instance or globally.
// </ul>
// Unless you're in an advanced scenario where you need to inject code before the above
// logic executes, place your initialization logic in initWidget() rather than init().  If you
// do decided to override this method, you must call the superclass implementation like so:
// <pre>
//    this.Super("init", arguments);
// </pre>
//
// @param	[arguments 0-N] (any)	All arguments initially passed to +link{Class.create()}
//
// @visibility external
//<
init : function (A,B,C,D,E,F,G,H,I,J,K,L,M) {
    
    //this.addProperties(isc.getRegisteredInstanceProperties(this.ID));
    if (isc._traceMarkers) arguments.__this = this;
	
	// get a global ID so we can be called in the global scope
	this.ns.ClassFactory.addGlobalID(this);

	// put this object in the master list of Canvases that have been created so it can be
    // deallocated later
	this._canvasList(true);

    // if position has not been set, default it to relative if htmlElement is set.
    if (this.position == null) {
        this.position = this.htmlElement != null ? isc.Canvas.RELATIVE : isc.Canvas.ABSOLUTE;
    }

    
    if (this.className != null && this.logIsInfoEnabled(this._$styleName)) {
        this.logInfo("'className' property specified. This property has been deprecated in "
                    + "favor of 'styleName' as of SmartClient 5.5.", this._$styleName);
    }
    if (this.styleName != null) {
        // both stylename and className are set
        if (this.className != null) {
            // Either .styleName and .className were explicitly passed in, or just one of the
            // properties was.
            // - respect the styleName passed in, if present, otherwise the className passed in
            var proto = this.getPrototype(),
                explicitStyle = (this.styleName != proto.styleName),
                explicitClassName = (this.className != proto.className);
            if (explicitStyle) this.className = this.styleName;
            else if (explicitClassName) this.styleName = this.className;
            // Both set on the prototype - respect the older .className attribute name
            else this.styleName = this.className;
            
        // .className property unset - just duplicate this.styleName
        } else {
            this.className = this.styleName;
        }
    // className set, but not styleName - copy it across
    } else if (this.className != null) {
        this.styleName = this.className;
    }

    // convenience for square components, only documented for Img/ImgButton
    if (this.size != null) this.height = this.width = this.size;
    
    // save the current width/height so we can tell if a width/height was explicitly set, and
    // default any dimensions that weren't set
    this._userWidth = this.width;
    this._userHeight = this.height;
    
    if (this.width == null) this.width = this.defaultWidth;
    if (this.height == null) this.height = this.defaultHeight;
    
    // copy the height property to this._height.  This is what we'll rely upon internally
    // for sizing 
    
    this._height = this.height;

    // Ensure margin / padding is a numeric value
    if (isc.isA.String(this.margin)) {
        var margin = parseInt(this.margin);
        if (isc.isA.Number(margin)) this.margin = margin;
        else {
            this.logWarn("Invalid setting for this.margin:" + this.margin + 
                         ". This should be a numeric value - ignoring");
            this.margin = null;
        }
    }
    if (isc.isA.String(this.padding)) {
        var padding = parseInt(this.padding);
        if (isc.isA.Number(padding)) this.padding = padding;
        else {
            this.logWarn("Invalid setting for this.padding:" + this.padding + 
                         ". This should be set to a numeric value - ignoring");
            this.padding = null;
        }
    }
    
    // Handle the case where a border was (incorrectly) specified as a number directly
    if (this.border != null && !isc.isA.String(this.border)) {
        this.border = this._convertBorderToString(this.border);
    }
    
    // resolve percentSource (if specified) to a widget, and observe it's inner size changing
    if (this.percentSource) this.setPercentSource(this.percentSource, true);
    
    // Call moveTo() and resizeTo() to resolve percentage positions and sizes to pixels.  If
    // coords are already numeric this is a no-op.
    // NOTE: if we have a parentElement, our percent size should be a proportion of it's size,
    // which we will only know when our parent draws us, so our parent tells us to
    // resolvePercentageSizes again right when we are about to draw().  However, if we delay
    // resolving this.width til draw: 
    // - classes that override draw() won't support percent width automatically
    // - it won't be safe to manipulate this.width until right before draw (eg, for layout)
    //   - if you had to call getWidth(), you could at least get a correct value for widths
    //     that are a percentage of the page
    // For this reason child layout code should generally run from layoutChildren(), right
    // before drawChildren().
    this._canvas_initializing = true; // HACK to allow resized() notifications to be ignored
    this.resizeTo(this.width, this._height);
    this.moveTo(this.left, this.top);
    this._canvas_initializing = null;

    // - Normalize children / peers properties into arrays
	if (this.children && !isc.isAn.Array(this.children)) this.children = [this.children];
	if (this.peers && !isc.isAn.Array(this.peers)) this.peers = [this.peers];

    // NOTE: we ensure a unique children Array via instantiateChildren.
    // The usage of declaring children as an instance prototype property is OK so long as the
    // children are specified as objects, not live Canvii, and none of classes involved assume
    // they have a unique copy of any shared subobjects.
    //if (this.children != null && this.children === this._prototype.children) {
        //this.logWarn("Detected children array as instance property")
        //this.children = this.children.duplicate();
    //}
    
    // If the deprecated 'enabled' property is set, set the disabled property to match it.
    
    if (this.enabled != this._$unset) {
        this.logWarn("Widget initialized with explicitly specified 'enabled' property. " + 
                     "This property has been deprecated - use 'disabled' instead.");
        this.disabled = !this.enabled;
    }
    
    if (this.redrawOnEnable != null) {
        this.logWarn("Widget initialized with deprcated 'redrawOnEnable' - use 'redrawOnDisable' instead.");
        this.redrawOnDisable = this.redrawOnEnable;
    }
    
	// call initWidget() to give each subclass of canvas a chance to initialize its child
    // structures
	this.initWidget(A,B,C,D,E,F,G,H,I,J,K,L,M);

    //>RoundCorners create a Canvas to show edges (eg rounded corners) around this Canvas
    this._createEdges();
    //<RoundCorners

    // automatically create a drop shadow
    if (this.showShadow) this._createShadow();

    //>CornerClips
    if (this.clipCorners) this._makeCornerClips(); //<CornerClips

    
	if (this.useBackMask && ((isc.Browser.isIE && isc.Browser.minorVersion >= 5.5)
                             || (isc.Canvas.useMozBackMasks && isc.Browser.isMoz))) {
        this.makeBackMask();
    }
    
    // Show group frame if appropriate
    if (this.isGroup) {
        delete this.isGroup;
        this.setIsGroup(true);
    }
    
    // Make sure each child and peer knows that it is not to auto-draw.  
    // This prevents the children from drawing outside of our context.
	// Do this after initWidget so that if you add a new child it gets the proper setting.
	if (this.children) this.children.setProperty(this._$autoDraw, false);
	if (this.peers) this.peers.setProperty(this._$autoDraw, false);

	// if the canvas has a 'observes' property, set those observations up
	if (this.observes) {

        var item,
            source,
            list = this.observes,
            len = list.length;

		for (var i = 0; i < len; i++) {
			// get the next item in the list
			var item = list[i];
			if (!item) continue;
			// if item.source is a string, treat it as a reference to a global object
			//	and call getGlobalReference() to get the reference to the actual object
			if (isc.isA.String(item.source)) source = this.getGlobalReference(item.source);
			else						 source = item.source;

			// if the source was found, set up the observation
			if (source) {
				this.observe(source, item.message, item.action);
			}
		}
	}

    // create child instances (if necessary) and add them as children
    this._instantiateChildren();

    // if any autoChildren are speicified in the autoChildren array, add them via the
    // addAutoChild() mechanism
    if (this.autoChildren) this.addAutoChildren(this.autoChildren);

    // designated for us by skins and instances to add autoChildren to existing components.
    // Custom components should not use this.
    if (this.addOns) this.addAutoChildren(this.addOns);
    
    //>!BackCompat 2004.08.05
    if (this._adjacentHandle && !this.drawContext) {
        this.drawContext = { element : this._adjacentHandle };
    } //<!BackCompat

    if (this.htmlElement) {
        var element = this.htmlElement;
        delete this.htmlElement;
        this.setHtmlElement(element);
    }    
    
    // If we have an eventProxy, add a pointer on that widget back to this one - used by EH.
    if (this.eventProxy != null) {
        if (!isc.isA.Canvas(this.eventProxy)) {
            this.logWarn("Canvas ID:'" +this.getID() + "' initialized with bad eventProxy. " + 
                         "This property should be set to another Canvas instance. Clearing this property.")
            delete this.eventProxy;
        } else {
            if (this.eventProxy._proxiers == null) this.eventProxy._proxiers = [];
            this.eventProxy._proxiers.add(this);
        }
    }

    // allow initialization of parentElement.
    // NOTE: needs to be done before autoDraw, and should probably be done after all of this
    // Canvii's children have been created
    var parentElement = this.parentElement;
    if (parentElement) {
        this.parentElement = null; // need to wipe this out or addChild with no-op
        if (isc.isA.String(parentElement)) parentElement = window[parentElement];
        //if (parentElement.children.contains(this)) this.logWarn("already contained!");
        parentElement.addChild(this);
    }

    //>!BackCompat 2009.7.7
    // We created and exposed 'autoFetchAsFilter' for the 7.0 release candidate builds.
    // If specified use it to override autoFetchTextMatchStyle 
    // databinding / 
    if (this.autoFetchAsFilter != null) {
        var aftms = this.autoFetchAsFilter ? "substring" : "exact";
        this.logWarn("This component has autoFetchAsFilter explicitly specified as:" +
                    this.autoFetchAsFilter + ". This attribute is deprecated in favor of " +
                    "this.autoFetchTextMatchStyle. Defaulting autoFetchTextMatchStyle to \"" +
                    aftms + "\" based on this setting.");
        this.autoFetchTextMatchStyle = aftms;
    }
    //<!BackCompat
    
	// if we're supposed to autoDraw, and we don't have a parentElement already,
	//	draw us now.  This allows us to avoid sprinkling canvas.draw() commands
	//	in our XML or JS source.
    //
    // isc.noAutoDraw is a special internal flag to suppress autoDraw even for components that
    // explicitly request it - currently used in the ExampleViewer where we reparent components
    // as part of example loading.
	if (this.autoDraw && !this.parentElement && !isc.noAutoDraw) {
        // XXX probably temporary workaround
        // in Safari, styling info isn't available until pageLoad, so we have a flag that
        // allows all autoDrawing to happen after page load, which works so long as no
        // application code has draw-dependant behaviors (eg checking sizes, or expecting
        // children to exist that are created at draw() time)
        if (isc.Browser.isSafari && isc.deferAutoDraw && !isc.Page.isLoaded() &&
            this.position != "relative") 
        {
            isc.Page.setEvent("load", this.getID()+".draw()");
        } else {
            this.draw();
        }
    }
},

//>	@method	canvas.initWidget()
//
// For custom components, perform any initialization specific to your widget subclass.
// <P>
// When creating a subclass of any Canvas-based component, you should generally override
// this method rather than overriding +link{Class.init()}.  This is because Canvas has its own
// +link{Class.init()} override which performs some generally desirable initialization - see
// +link{Canvas.init()} for details.
// <p>
// This method is called by +link{Canvas.init()} when a component is create()d.  When
// overriding this method, You must call the superClass initWidget implementation, like
// so:
// <pre>
//    this.Super("initWidget", arguments);
// </pre>
// <P>
// In general, if you are going to call functionality supported by your superclass (eg calling
// addTab() when your superclass is a TabSet), call Super() first.  However, you can generally
// assign properties to <code>this</code> before calling Super() as a way of mimicing the
// effect of the property being passed to +link{Class.create(),create()} on normal instance
// construction.  For example, when subclassing a DynamicForm, you could set this.items to a
// generated set of items before calling Super().
// <P>
// NOTE: child creation: if you are creating a component that auto-creates certain children (eg
// a Window which creates a Header child), typical practice is to create those children
// immediately before drawing by overriding draw().  This postpones work until it is really
// necessary and avoids having to update children if settings are changed between creation and
// draw().  Alternatively, if you prefer callers to directly manipulate auto-created children,
// it's best to create them earlier in initWidget(), in order to allow manipulation before
// draw.
//
// @param	[arguments 0-N] (any)	All arguments initially passed to +link{Class.create()}
//
// @visibility external
//<
initWidget : function () { },
_$initWidget : "initWidget", // for calling Super

//>EditMode 
setID : function (id) {
    // leave the old global name intact so that generated expressions that depend on a stable
    // id (such as timers or page events) continue to work.  Adding an entry to pointersToThis
    // means we'll clean up our old name on destroy.  However this won't handle renaming a
    // widget to the old name of another, so this is strictly editMode.
    var pointersToThis = this.pointersToThis = this.pointersToThis || [];
    pointersToThis.add({ object: window, property: this.ID });

    this.ID = id;

    // new global reference
    window[this.ID] = this;

    // regenerate DOM, which may have inline event handlers pointing to our old ID
    this.clear(); this.draw();
},
//<EditMode

// Drawn state
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getDrawnState()
//      What state of drawing is this canvas in? Options:
//      - undrawn       :   has never been drawn or has been cleared
//      - drawingHandle :   in the process of drawing - handle not yet fully written out to the DOM
//      - handleDrawn   :   handle completely written out to the DOM, but children, peers etc. not drawn
//      - complete      :   fully drawn, including children, peers, etc.
//		@group	drawing
//
//<
getDrawnState : function () {
    // Note: In theory only one of the 3 possible drawing flags should be true at a time 
    // (guaranteed if cavas.setDrawnState() is used to set the drawn state).
    // However, check them in reverse order from which they would be applied, in case
    // this._drawn gets set on drawing completion, but this._handleDrawn is not cleared at the same 
    // time.
    if (this._drawn == true) return isc.Canvas.COMPLETE;
    if (this._handleDrawn == true) return isc.Canvas.HANDLE_DRAWN;
    if (this._drawingHandle == true) return isc.Canvas.DRAWING_HANDLE;
    return isc.Canvas.UNDRAWN;

},

//>	@method	canvas.setDrawnState()
//      Set state of drawing for this canvas.  
//      This should be called rather than directly setting this._drawn, etc. flags.
//      Options:
//      - undrawn       :   has never been drawn or has been cleared
//      - drawingHandle :   in the process of drawing - handle not yet fully written out to the DOM
//      - handleDrawn   :   handle completely written out to the DOM, but children, peers etc. not drawn
//      - complete      :   fully drawn, including children, peers, etc.
//		@group	drawing
//
//<
setDrawnState : function (state) {
    if (state == isc.Canvas.COMPLETE) this._drawn = true;
    else this._drawn = false;
    
    if (state == isc.Canvas.HANDLE_DRAWN) this._handleDrawn = true;
    else this._handleDrawn = false;
    
    if (state == isc.Canvas.DRAWING_HANDLE) this._drawingHandle = true;
    else this._drawingHandle = false;
    
    // If state is isc.Canvas.UNDRAWN, no need to do anything as we will have already cleared
    // all the drawn flags
},


//>	@method	canvas.isDrawn()    ([])
//      Returns the boolean true, if the widget has been completely drawn, and false otherwise.
//  @visibility external
//  @group	drawing
//  @return	(boolean)	true if drawn, false if not drawn
//<
isDrawn : function () {
	return !!this._drawn;
},

handleDrawn : function () {
    return !!this._handleDrawn;
},

// ----------------------------------------------------------------------------------------

//>	@method	canvas.getID()  ([])
//      When a widget instance is created, it is assigned a unique global identifier that can be
//      used to access the instance by name. The getID method returns this ID for a particular
//      instance. Global IDs are essential when you need to embed a widget reference in a string,
//      usually a string that will be evaluated in the future and/or in another object, where you
//      may not have access to a variable or parameter holding the widget's reference.
//
//  @visibility external
//  @return	(string)	global identifier for this canvas
//<
getID : function () {
    if (this.ID == null) this.ns.ClassFactory.addGlobalID(this);
	return this.ID;
},

// so that we look more like DOM objects
getAttribute : function (attributeName) { return this[attributeName] },

// Drawing
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getInnerHTML() ([A])
//      Return the inner HTML for this canvas. Called when the canvas is drawn or redrawn; 
//      override to customize.
//		@group	drawing
//
//      @visibility external
//		@return	(string)	HTML contents of this canvas
//<
getInnerHTML : function () {
    var html;	
    if (!this.containsIFrame()) html = this.getContents();
    else {
        var url = this.getContentsURL();
        // support special prefixes, eg [APPFILES]
        url = isc.Page.getURL(url);
        // support params, actually doc'd under HTMLFlow only
        if (isc.rpc) url = isc.rpc.addParamsToURL(url, this.contentsURLParams);
    
        
        isc.EventHandler.registerMaskableItem(this, true);
        html = this.getIFrameHTML(url);
    }
    return html;
},

getIFrameHTML : function (url) {
    // XXX: may need to be updated when/if we add code to auto-size to contents of iframe.
    
    return "<iframe height='100%' width='100%' scrolling='" +
        (this.overflow == isc.Canvas.HIDDEN ? "no'" : "auto'") +
        (isc.Browser.isSafari ? " id=" + this._getDOMID("iframe") : "") +
        " frameborder='0'" +
        " src=\"" + url +"\"></iframe>";
},

// In FireFox through 2.0, IFRAMEs with 100% just don't obey in anything other than full quirks
// mode (no DOCTYPE at all), so we manually resize after draw and resize.  Widely reported
// issue:
// http://www.google.com/search?q=firefox+iframe+100%25+height
_sizeIFrame : function () {
    var drawnState = this.getDrawnState();
    if (drawnState != isc.Canvas.COMPLETE && drawnState != isc.Canvas.HANDLE_DRAWN) return;

    var handle = this.getHandle(),
        frameElement = handle ? handle.firstChild : null;
    // handle should be drawn so these sanity checks to get at the frame element may be
    // unnecessary.
    if (frameElement == null) return; // should never happen

    //this.logWarn("Resizing IFRAME to: " + this.getInnerHeight());
    // Note: Even though getInnerContentHeight() already accounts for padding, and the
    // iframe has no margin or border, it will increase scrollHeight of the handle by 2px more
    // than the specified size of the iframe. Adjust for this so we don't get widget-level
    // scrollbars in addition to any native scrollbars on the iframe itself.
    frameElement.style.height = (this.getInnerContentHeight() -2) + isc.px;
},

// internal signature, allows timing all getInnerHTML overrides from the Canvas level
_getInnerHTML : function (dontConcat) {
    
    var HTML = this.getInnerHTML(dontConcat);
    
    if (this._appendHTML) {
        var appendHTML = this._appendHTML.join(isc.emptyString);
        HTML = (HTML == null || HTML == isc.nbsp ? appendHTML : HTML + appendHTML);
    }
    return HTML;
},



//>	@method	canvas.readyToDraw()
// Determines whether there's any reason why draw() should not proceed and draw the canvas at this
// time.  Logs errors and warnings if appropriate, so if you override draw() just add a check
// like:<br>
// &nbsp;&nbsp;if (!this.readyToDraw()) return this;
//		@group	drawing
//
//		@return	(boolean)	True if draw() should proceed.
//<
readyToDraw : function () {

    // If we're already drawn, or in the process of drawing, log a warning and return false
    var drawingState = this.getDrawnState();
    if (this.getDrawnState() != isc.Canvas.UNDRAWN) {
        var drawingState = this.getDrawnState();
        
        this.logWarn("draw() called on widget with current drawn state: " + drawingState + 
                     (drawingState == isc.Canvas.COMPLETE ? 
                      ", use redraw() instead." : ", ignoring.") + this.getStackTrace(),
                     "drawing");
        return false;
    }

    // If showIf returns false, we're not ready to draw.
    // if showIf has not been overridden don't bother to evaluate it
    if (this.showIf != null) {
        // CALLBACK API:  available variables:  "canvas"
        // Convert a string callback to a function
        this.convertToMethod("showIf");
        
        // don't draw if the showIf returns false
        // (Will still draw if this function returns no explicit value - EG observation function / 
        //  no-op function)
        if (this.showIf(this) == false) return false;
    }

    // refuse to draw if we have zero or negative area
    if (this.getHeight() <= 0 || this.getWidth() <= 0) {
        
        if (this._pendingPageResizeForModalDialog) {
            this.drawDeferred();
            return false;        
        }
    
        // NOTE: drawing with negative area appears to work in all browsers, then in Nav4 you get
        // non-local partial failures
		//>DEBUG
		this.logWarn("negative or zero area: height: " + this.getHeight() + ", width: " + this.getWidth() +
                     ", refusing to draw" + this.getStackTrace(), "drawing");
		//<DEBUG
        return false;
    }

    // If we have a pending delayed draw event, don't draw.
    // (NOTE: we could have this clear that event, and attempt to proceed instead, but
    //  the desired usage of the deferred draw feature is simply to delay drawing while it is
    //  not legal for some reason...
    //  If we instead wanted to have a draw occur on some other event like a click, we'd probably
    //  use a call to show() at the appropriate time instead).
    if (this.deferredDrawEvent != null) {
		//>DEBUG
		this.logInfo("draw() called while object already pending a delayed draw - no action to take", 
                        "drawing");
		//<DEBUG
		return false;
    }

    // If we have a parent element:
    //
    // If the parentElement has written out it's start tag it's ok to proceed, as we'll 
    // document.write() the child in the correct scope
    // If the parentElement has completely written out it's HTML, it's ok to proceed, as we'll 
    // use the _insertHTML method to add the child to the parent using the DOM.
    //
    // If the parent has never drawn though we don't want to proceed, as we will either 
    // document.write() into the wrong scope, or attempt to _insertHTML() into a non-existant handle
    if (this.parentElement != null && 
            (!isc.isA.Canvas(this.parentElement) || 
             this.parentElement.getDrawnState() == isc.Canvas.UNDRAWN) )
    {
        this.logWarn("Attempt to draw child of an undrawn parent - ignoring" +
                     this.getStackTrace(), "drawing");
        return false;
    }

    //>Safari
    if (isc.Browser.isSafari && !isc.Page.isLoaded()) {
        var safariVersion = isc.Browser.safariVersion;
        
        if (parseInt(safariVersion) < 100) {
            this.drawDeferred();
            return false;
        
        } else {
            if (!isc.Canvas._safariDeferDrawWarned) {
                isc.Canvas.logWarn(
                    "Isomorphic recommends drawing components after page load in Safari, as " +
                    "some sizing information may not be available until the page has " +
                    "completely loaded.  If you are encountering sizing issues for any " +
                    "component try setting 'autoDraw' to false, and setting up an event " +
                    "to draw the component on the Page level 'load' event.",
                    "drawing"
                );
                isc.Canvas._safariDeferDrawWarned = true;
            }
        }
    }
    //<Safari
    
    // Otherwise it's ok to draw.
    return true;

},

// determine whether the Canvas must draw via doc.write(), which is needed for Canvii created
// by isc.Canvas.start(), in order to allow any <SCRIPT> blocks embedded in their
// Canvas.contents to execute in IE.
// Automatically checks for situations where its impossible to use doc.write() (parent handle
// already drawn).
_mustDocumentWrite : function () {
    
    return false;
},
// determine whether a Canvas would like to draw via doc.write()
_requestsDocumentWrite : function () {
    // flag set by isc.Canvas.start()
    if (this._forceDocumentWrite) return true;

    // if a parent is in the middle of drawing via doc.write()ing, we have to draw via
    // doc.write() or we won't insert into the parent.  A parent will be forced to doc.write()
    // if any of its children must doc.write() - and once it's doc.write()ing, all its children
    // must do so - otherwise they'll crash while looking for the parent handle (which is only
    // partially written in this mode)
    var theParent = this.parentElement;
    while (theParent) { 
        if (theParent._forceDocumentWrite) return true;
        theParent = theParent.parentElement;
    }

    // if we have any children, recursively, that need to document.write(), then we have to use
    // document.write() 
    if (this.children) {
        //this.logWarn("checking children: " + this.children.getProperty("_forceDocumentWrite"));
        for (var i = 0; i < this.children.length; i++) {
            if (this.children[i]._mustDocumentWrite()) return true;
        }
    }
    return false;
},

//>	@method	canvas.draw()   ([])
//      Draws the widget on the page.
//  @visibility external
//  @group  drawing
//  @return (canvas)    Pointer to this canvas.  Returned so statements like the following will
//						work:<br>
//                          var myCanvas = Canvas.newInstance({...}).draw();
//<
//  "showing" parameter: if true, draw() was called from show().  By default draw() will call show()
//  on completion if the widget is visible as some widgets use show() as an entry point to
//  initialize certain widget properties.  If draw() was called from show(), always avoid calling
//  show() a second time.
draw : function (showing) {
    
    if (isc._traceMarkers) arguments.__this = this;
    // verifies that it is legal to draw
    if (!this.readyToDraw()) return this;
    
    
    
    // auto-assigned tab-index:
    // If this widget is focusable, getTagStart will call 'getTabIndex()' to determine the 
    // tabIndex to write into the handle / focusProxy.
    // If this widget has no specified tabIndex, this method will auto-assign the next available
    // tab index to this widget.
    // The behavior this gives us is that focusable canvii with no specified tab index are 
    // inserted into the tab order for the page in the order in which they are drawn, which
    // is appropriate.
    // We make any widgets showing scrollbars focusable by default.  If a widget has 
    // overflow:auto, however we don't know whether it will be scrollable until it has been
    // drawn and adjustOverflow gets fired.  This can happen on a delay / after page load in
    // some browsers, so that widget will end up at the end of the page's tab order rather than
    // at the expected position.
    // Avoid this issue by calling 'getTabIndex()' on draw() for any overflow:auto widgets to
    // ensure auto-allocated tab-indices are allocated in the order in which these are drawn
    // onto the page.
    
    if (this.overflow == isc.Canvas.AUTO) this.getTabIndex();
    
	//>DEBUG
    if (this.logIsInfoEnabled(this._$draws)) {
    	this.logInfo("draw(): drawing " + this.Class +
                     (this.parentElement ? " with parent: " + this.parentElement : "") +
                     (!isc.Page.isLoaded() ? " before page load" : "") +
                     (this.logIsDebugEnabled(this._$draws) ? this.getStackTrace() : ""), 
                     this._$draws);
    }
    // track total draws
    this._addStat(this._$draws);
    
    //<DEBUG
    // If we are databound, and autoFetchData is true, do a one time fetch on initial draw.
    
    var fetchQueued = false;
    if (this.autoFetchData && !this._initialFetchFired && this.fetchData) {
 
        if (!this.dataSource) {
            this.logWarn("autoFetchData is set, but no dataSource is specified, can't fetch");
        } else {
            // Queue the fetch - this means we can batch up any requests our children make on draw
            // and send them all off together
            // Specific use case: this means if a ListGrid is autoFetchData:true and has a field
            // with an optionDataSource we can use the same transaction to fetch the valid options
            // as to fetch the LG data
            fetchQueued = !isc.RPCManager.startQueue();
            // getInitialCriteria() picks up this.initialCriteria
            // getInitialFetchContext() picks up this.autoFetchTextMatchStyle            
            this.fetchData(this.getInitialCriteria(), null, this.getInitialFetchContext());
            
            this._initialFetchFired = true;
        }        
    }
    // If we have any peers, call the 'predrawPeers()' method.
    // This method will draw any peers marked with the "_drawBeforeMaster" flag (set up by passing
    // a parameter to addPeer()), before continuing with the drawing process.
    // We do this because it allows developers to specify some peers to be drawn before the master
    // gets drawn, when the master is controlling the drawing of the peers.
    // This is valuable in some cases as the master is guaranteed that such preDraw peers will be
    // drawn while its own draw() is firing - for example this is used in stretchImgButtons, where
    // the getInnerHTML() method makes use of the drawn size of the label (peer) for the button
    // to determine the desired size of the images.
    // See the methods:
    //  predrawPeers(), drawPeers(), redrawPredrawnPeers(), redrawPeers() and addPeer()
    if (this.peers != null && this.peers.getLength() > 0) {
        
        
        this.predrawPeers();
        
    }
 
    
    if (isc.Browser.isIE && this.fixIEOpacity && !this.masterElement) {
        //this.logWarn("checking for parent opacity");
        var parent = this.parentElement;
        while (parent) {
            if (parent.opacity != null && parent.opacity != 100) {
                //this.logWarn("opacity set on parent: " + parent);
                this.setOpacity(100, null, true);
                break;
            }
            parent = parent.parentElement;
        }
    }
    
    // if this.htmlElement and this.matchElement are set, resize the canvas to fit the
    // target element before drawing
    if (this.htmlElement != null && this.matchElement) {
        if (isc.isA.String(this.htmlElement)) this.htmlElement = isc.Element.get(this.htmlElement);

        // We want the available inner width of the element.
        
        // For things that don't overflow this will be the specified width or, if there isn't
        // one, the clientWidth - padding
        
        var width = isc.Element.getNativeInnerWidth(this.htmlElement),
            height = isc.Element.getNativeInnerHeight(this.htmlElement);
        this.setWidth(width);
        this.setHeight(height);        
    }

    

    var parentElement = this.parentElement;        
    var mustDocumentWrite = 
        // the page is not loaded
        (!isc.Page.isLoaded() && 
         // if this widget is going to draw at a specified, pre-existing position in the DOM,
         // it needs to do DOM insertion, not document.write()
         !this.drawContext && 
         // this Canvas is relative and has no parent, hence needs to doc.write() HTML at
         // the current document position.  Note that children of a relatively positioned
         // parent insert into the parent via DOM insertion, not doc.write()
         (parentElement == null && this.position == isc.Canvas.RELATIVE)
        );

    
    mustDocumentWrite = mustDocumentWrite || this._mustDocumentWrite();

    // Determine whether we should insert innerHTML as a separate commit.  This allows children
    // to be drawn before, during or after innerHTML generation.
    var separateContentInsertion = this.separateContentInsertion;
        
    if (isc.Page.isLoaded() || !mustDocumentWrite) {
        
        
        
        this._insertHTML(!separateContentInsertion);
        
        
        
        if (separateContentInsertion) this._updateParentHTML();
        
        this.drawChildren();
        
        
        this._completeHTMLInit();
        
        
	} else {
        
        
        var parent = this.parentElement;

        // detect the case of getting fooled about whether the page is loaded and fix it in
        // IE, warning for other browsers.
        if ((isc.Browser.isOpra || isc.Browser.isIE) && this.getDocument().readyState == "complete")    
        {
            isc.Page.finishedLoading();
            
        }
        
        
        this._writeHTML();
        
        
	}

    // If we queued the fetch, lets send it off now
    if (fetchQueued) isc.RPCManager.sendQueue();    
    
    //>FocusProxy If we're using a focusProxy, create it now
    if (this._useFocusProxy && this._canFocus()) this.makeFocusProxy();
    //<FocusProxy

    
    if (this.accessKey != null && this._useAccessKeyProxy() && this._canFocus()) {
        this._makeAccessKeyProxy();
    }
    
    // If we're enforcing scroll size, ensure the scroll-size-enforcer div is present
    if (this._enforcingScrollSize != null) 
        this.enforceScrollSize(this._enforcingScrollSize[0], this._enforcingScrollSize[1]);
    
    // If we're masked by a hard clickmask, ensure EH takes the necessary 
    // action to physically mask us
    
    if (this._isHardMasked()) isc.EH._hardMaskTargets([this]);
        
    //>CornerClips
    if (this.clipCorners) this._finishCornerClips(); //<CornerClips
   
    
   
    // At this point we've written out the HTML into the DOM.
    // If the widget is visible, call show() on it; certain widgets override show() to do
    // perform miscellaneous tasks associated with displaying the widget - we would want to
    // perform these when the widget is drawn, too
    if (!showing && this.isVisible()) this.show();
    // for uses like Canvas.draw().moveTo(...)
    
    
    
    // notify parent / masterElement that we've been drawn
    if (this.parentElement) this.parentElement.childDrawn(this);
    if (this.masterElement) this.masterElement.peerDrawn(this);
    
    // If we're relatively positioned, and we're a top level canvas, fire moved() on page 
    // resize to allow our peers to reposition themselves
    // Note: this initial setup needs to fire on draw even if the widget is not visible
    // as our peers still need to be informed of when the masterElement moves.
    if (this.parentElement == null && this.position == this._$relative) {
        // remember our current page coordinates
        this._preResizePageLeft = this.getPageLeft();
        this._preResizePageTop = this.getPageTop();
        isc.Page.setEvent(
            "resize", 
            this,
            isc.Page.FIRE_ONCE,
            "_relativePageResized"
        );
    }
    
    
    
    
    return this;
},

// getInitialCriteria() - used to retrieve the initialCriteria when performing auto-fetch of data
getInitialCriteria : function () {
    return this.initialCriteria;
},


getInitialFetchContext : function () {
    var context = {};
    context.textMatchStyle = this.autoFetchTextMatchStyle;
    return context;
},
            

// output this widget's HTML via document.write()
_writeHTML : function () {

    // mark that we've started drawing the handle - this allows us to detect recursive calls to 
    // draw() and other invalid cases.
    this.setDrawnState(isc.Canvas.DRAWING_HANDLE);

    var doc = this.getDocument(),
        separateContentInsertion = this.separateContentInsertion;
    
    if (this.children != null && this._mustDocumentWrite()) {
        // now that the parent is using doc.write(), all children must do so as well,
        // regardless of whether the children are marked _forceDocumentWrite - otherwise
        // they'll try to look up our handle for insertAdjacentHTML() and fail because it'll
        // only be partially written.
        this._forceDocumentWrite = true;

        //this.logWarn("using legacy doc.write() path");
        
        var tagStart = this.getTagStart(),
            tagEnd = this.getTagEnd();
        doc.write(separateContentInsertion ? tagStart : tagStart + this._getInnerHTML())
        this.drawChildren();
        doc.write(separateContentInsertion ? this._getInnerHTML() + tagEnd : tagEnd);

        // Mark that we've finished drawing the handle into the DOM
        this.setDrawnState(isc.Canvas.HANDLE_DRAWN);

    } else {

    // write the complete parent handle into the DOM, then have children, if any, insert into
    // completed handle
    doc.write(
        isc.SB.concat(this.getTagStart(), 
                      (separateContentInsertion ? null : this._getInnerHTML()),
                      this.getTagEnd()
        )
    );

    // Mark that we've finished drawing the handle into the DOM
    this.setDrawnState(isc.Canvas.HANDLE_DRAWN);
		
    // if we are separately inserting content, insert the parent's content now.  Note that it
    // is legal for some children to get manually drawn at this point, which allows parents to
    // write out content that is dependant on child sizes.
    if (separateContentInsertion) this._updateParentHTML();

    // draw children if we have any
    this.drawChildren();

    }

    // call completeHTMLInit to draw peers, set up events, adjustOverflow, and mark us as
    // completely drawn
    this._completeHTMLInit();
    
    
    if (isc.Browser.isMoz && this.getScrollingMechanism() == isc.Canvas.NATIVE) 
        this.checkNativeScroll();
    
	// return a pointer to the object
	return this;
},

// Draw after a pause (by default on page load or the next idle)
// Holds onto the deferred draw event as this.deferredDrawEvent, so you can keep track of
// cases where we're waiting to draw, and do the appropriate thing with calls to clear, etc.

drawDeferred : function () {

    var eventType = (isc.Page.isLoaded() ? "idle" : "load");
    
    if (this.deferredDrawEvent != null) {
		this.logInfo("drawDeferred() called when object is already pending drawing " + 
                        "- No action to take.");
    
        return;
    }
    
    var ID = this.getID();
    
    this.deferredDrawEvent = 
            isc.Page.setEvent(eventType, 
                              "delete " + ID + ".deferredDrawEvent;" + ID + ".draw();", 
                              isc.Page.FIRE_ONCE);
},

// Printing
// --------------------------------------------------------------------------------------------



//> @groupDef Printing
// The browser's built-in support for printing will at best print what you see, which in the
// case of a web application will often be useless, illegible, or partial.
// <P>
// SmartClient has specialized printing support that can take any page built with SmartClient
// components and provide a reasonable printed view.  The default printed view:
// <P>
// <ul>
// <li> renders components without clipping or scrolling regions, so that a scrolling grid
// shows all loaded rows
// <li> removes certain decorative images, such as image-based backgrounds, which may print
// poorly in black and white
// <li> converts editing controls into static representations of the data being edited
// <li> removes interactive elements such as buttons and menus, which don't work on paper and
// waste space
// </ul>
// The default printed view can be customized with settings and method overrides as necessary,
// including the ability to created printed representations of custom components you have
// created.
//
// @visibility printing
//<

//>	@method	canvas.getPrintHTML() [A]
// Retrieves printable HTML for this component and all printable subcomponents.
// <P>
// By default any Canvas with children will simply collect the printable HTML of its
// children by calling getPrintHTML() on each child that is considered
// +link{canvas.shouldPrint,printable}.  
// <P>
// Any Canvas that does not have children will return the result of +link{getPrintInnerHTML}.
//
// @visibility printing
//<
// @attr printProperties (object) properties to configure printing behavior - may be null.
// @attr callback (callback) optional callback. This is required to handle cases where HTML 
// generation is asynchronous - if a method generates HTML asynchronously, it should return
// null, and fire the specified callback on completion of HTML generation
_$html:"html",
getPrintHTML : function (printProperties, callback) {
//!DONTOBFUSCATE  (obfuscation breaks the inline function definitions)

    this.isPrinting = true;
    // Always copy this.printProperties onto the printProperties block passed in
    // [Allows you to always suppress controls for certain components only, etc.]
    if (printProperties == null) {
        printProperties = isc.addProperties({},this.printProperties);
        // store the top level canvas so we know not to writing out positioining info
        // for it.
        printProperties.topLevelCanvas = this;

        // omitControls / includeControls
        // omitControls is an array of widget classes which should be ommitted as they are
        // controls.
        // By default all subclasses of these controls will also be ommitted - however we can
        // override that behavior by including a subclass in the 'includeControls' array
        printProperties.omitControls = isc.Canvas.printOmitControls;
        printProperties.includeControls = isc.Canvas.printIncludeControls;
        
        printProperties.isDrawn = this.isDrawn();
        printProperties.isVisible = this.isVisible();

    } else {
        printProperties = isc.addProperties(printProperties, this.printProperties);
    }


    
    this.currentPrintProperties = printProperties || {};
    var HTML = [this.getPrintTagStart(), , ,this.getPrintTagEnd()];

    // Omit content if we have children (matches standard rendering behavior)
    // - Check this.children - this may include children (such as controls) which are omitted from
    //   the print children [but still mean the content needs to be rendered]
    if (!this.children || this.children.length == 0 || this.allowContentAndChildren) {
        HTML[1] = this.getPrintInnerHTML();
    }
    
    // Not all print properties should be passed onto our children.
    // clear the "inline" setting before passing the printProperties block on.
    delete printProperties.inline;

    // clear up any gaps etc in the user-defined omitComponents block
    if (printProperties.omitComponents) {
        var omitComponents = printProperties.omitComponents
        for (var i = 0; i < omitComponents.length; i++) {
            if (isc.isA.String(omitComponents[i]))
                omitComponents[i] = window[omitComponents[i]];
            if (!isc.isAn.Instance(omitComponents[i])) omitComponents[i] = [];
        }
        omitComponents.removeEmpty();
    }

    var children = this.getPrintChildren();
    
    var self = this;

    var completePrintHTML = this.getCompletePrintHTMLFunction(HTML, callback);
    
    if (children) {
        var childrenHTML = [],
            childCount = children.length,
            completedCount = 0
        ;

        var childHTMLComplete = function (childIndex, html) {
            childrenHTML[childIndex] = html;
            ++completedCount;
            //self.logWarn("Child: " + childIndex + " - " + completedCount + "/" + childCount);
            if (completedCount == childCount) {
                return completePrintHTML(childrenHTML);
            }
            return null;
        }

        var thisHTML = null;
        for (var i = 0; i < childCount; i++) {
            var child = children[i];

            //this.logWarn("HTML for child: " + i);

            // assembly body string to hard-code index of HTML insertion point to ensure
            // correct order
            var func = (function (i) {
                return function (html) {
                    childHTMLComplete(i, html);
                }
            })(i);

            // ask child to generate HTML and pass optional async callback
            var childHTML = this.getChildPrintHTML(child, printProperties, func);

            // child didn't go async, so it won't be calling its async callback - so we need
            // to do it.
            if (childHTML !== null) {
               //this.logWarn("child: " + i + " returned HTML synchronously");
               thisHTML = childHTMLComplete(i, childHTML);
            } /* else {
               this.logWarn("child: " + i + " went async -> " + child.getID());
            } 
            */
        }
        return thisHTML;
    } else {
        // no children, finish up
        return completePrintHTML();
    }
},

getChildPrintHTML : function (child, printProperties, callback) {
    return child.getPrintHTML(printProperties, callback);
},

getCompletePrintHTMLFunction : function (HTML, callback) {
    // The HTML / callback params will be available due to JS closure
    var self = this;
    return function (childrenHTML) {
        self.isPrinting = false;
        
        if (isc.isAn.Array(childrenHTML)) childrenHTML = childrenHTML.join(isc.emptyString);
        if (childrenHTML) HTML[2] = childrenHTML;
        
        HTML = HTML.join(isc.emptyString);
        delete self.currentPrintProperties;
        if (callback) {
            self.fireCallback(callback, "HTML,callback", [HTML, callback]);
            return null;
        } else {
            //self.logWarn("completePrintHTML() - no callback, returning HTML");
            return HTML;
        }
    }
},


getPrintInnerHTML : function () {
    return this._getInnerHTML();
},

// getPrintChildren() -- returns the set of children we will include in our printHTML
// Split into a separate method for ease of overriding
getPrintChildren : function () {
    var children = this.children;
    if (!children || children.length == 0) return;
    var printChildren = [];
    for (var i = 0 ; i < children.length; i++) {
        if (this.shouldPrintChild(children[i])) printChildren.add(children[i]);
    }   
    return (printChildren.length > 0) ? printChildren : null;
},
 
//> @attr canvas.shouldPrint (boolean : null : IRW)
// Whether this canvas should be included in a printable view.
// <P>
// Default is to:
// <ul>
// <li> omit all peers (edges generated by showEdges:true, etc)
// <li> omit anything considered a "control", such as a button or menu (see
// +link{PrintProperties.omitControls})
// <li> include everything else not marked shouldPrint:false
// </ul>
//
// @group printing
// @visibility printing
//<

// shouldPrintChild - called by getPrintChildren() to determine which children need printing
shouldPrintChild : function (child) {

    if (child.shouldPrint != null) return child.shouldPrint;

    // omit peers for now to suppress edges, backmask, etc.
    if (child.masterElement) return false;

    var printProperties = this.currentPrintProperties,
        omitControls = printProperties.omitControls,
        omitComponents = printProperties.omitComponents;

    if (!isc.isAn.Instance(child) || 
        (omitComponents && omitComponents.contains(child))) 
    {        
        return false;
    }
    // omitControls is an array of widget classNames to skip
    if (omitControls) {
        // exception, if control is present in "includeControls" array, don't skip it. This is
        // useful for cases where we have a specific subclass of an ommitted controls class which
        // we want to include
        
        var includeControls = printProperties.includeControls;
        if (includeControls && includeControls.length > 0) {
            for (var i = 0; i < includeControls.length; i++) {
                var cName = includeControls[i];
                if (isc.isA[cName] && isc.isA[cName](child)) return true;
            }
        }
        for (var i = 0; i < omitControls.length; i++) {
            var cName = omitControls[i];
            if (isc.isA[cName] && isc.isA[cName](child)) {
                return false;
            }
        }
    }
    
    // If a developer calls getPrintHTML() on something undrawn or hidden directly we should
    // respect it. However if the method is called on a parent with undrawn/hidden children
    // we should skip the children by default.
    if ((!child.isDrawn() && printProperties.isDrawn) ||
         (!child.isVisible() && printProperties.isVisible)) return false;
        
    return true;
},

// _fixPNG() -- apply the .png workaround in IE.
// Will only be called if _fixPNG() is true at the Canvas level - allows us to disable 
// the png workaround for specific canvii on the fly (EG when printing)
_fixPNG : function () {
    if (this.isPrinting) return false;
    return true;
},

getPrintStyleName : function () {
    return this.printStyleName || this.styleName;
},

// getPrintTagStart / end -- returns the DIV / SPAN tags written out around our HTML in printing
// mode.
getPrintTagStart : function () {
    var props = this.currentPrintProperties,
        topLevel = props.topLevelCanvas == this, 
        inline = !topLevel && props.inline,
        className = this.getPrintStyleName();
        
    return [(inline ? "<span " : "<div "),
            (className ? "class='" + className + "' " : null),
            // could add borders etc here
            this.getPrintTagStartAttributes(),
            ">"].join(isc.emptyString);
},

getPrintTagStartAttributes : function () {
    return null;
},

getPrintTagEnd : function () {
    var props = this.currentPrintProperties,
        topLevel = props.topLevelCanvas == this, 
        inline = !topLevel && props.inline;
    return inline ? "</span>" : "</div>";
},


// Backmask
// --------------------------------------------------------------------------------------------

//>BackMask

makeBackMask : function (props) {
    // in Moz, defer backmask creation until page load.  Otherwise the pre-page load heuristics
    // kick in for the iframe, causing crazy rendering (iframe burns through what it's supposed
    // backmask)
    if (isc.Browser.isMoz && !isc.Page.isLoaded()) {
        this._deferredBackMaskProps = props;
        isc.Page.setEvent("load", this, isc.Page.FIRE_ONCE, "makeBackMask");
        return;
    }
    // Note: there's code in BrowserPlugin.draw() that somewhat hackishly reaches into the
    // _deferredBackMaskProps, so be careful if you make changes to this.
    if (this._deferredBackMaskProps) {
        props = this._deferredBackMaskProps;
        delete this._deferredBackMaskProps;
    }
	this._backMask = isc.BackMask.create(props);
	this.addPeer(this._backMask);
  	this._backMask.setZIndex(this.getZIndex(true)-2);    
    this._sizeBackMask();
},
//<BackMask

// Focus Proxy
// --------------------------------------------------------------------------------------------



//>FocusProxy create the focusProxy to manage focus for this Canvas
makeFocusProxy : function () {
    // This is actually an almost trivial wrapper for _makeFocusProxy, allowing us to set a 
    // '_makingFocusProxy' flag on this widget, and clear it on return, without having to clear
    // it in every possible return case from the function that does the work

    // Bail if
    // - we're not using focusProxies on this element
    // - we already have a focusProxy written into the DOM
    // - we're already running this method (so a call to getTabIndex() or something else fell back
    //   through to this method)
    // - we're not drawn
    // - we're waiting to create a f.p on page load
    if (!this._useFocusProxy || this._hasFocusProxy || this._makingFocusProxy || !this.isDrawn() 
        || this.__delayedMakeFocusProxy != null) return;

    // set a temporary flag that we're in the middle of creating a focusProxy
    this._makingFocusProxy = true;        
    
    this._makeFocusProxy();
    
    this._makingFocusProxy = null;
},

_makeFocusProxy : function () {    
    // We know that the widget's handle is completely drawn at this point - therefore we can
    // use insertAdjacentHTML to write the focusProxy handle next to the widget's handle.
    // Note - if the page isn't loaded, using insertAdjacentHTML() to plug the handle into the
    // DOM can cause crashes on some browsers.
    
    if (!isc.Page.isLoaded() && isc.Browser.isSafari) {  
        // call this.getTabIndex() to force auto-allocation of tab index to occur in the
        // expected order
        this.getTabIndex();
        // Delay actually writing out the focusProxy until the page is loaded to avoid problems
        // with manipulating the DOM before page load
        this.__delayedMakeFocusProxy = 
            isc.Page.setEvent("load", this, null, "delayedMakeFocusProxy");

        return;
    }

    var tabIndex = this.getTabIndex();
    if (this.isDisabled()) tabIndex = -1;
    
    if (isc.Browser.isSafari && tabIndex == -1) {
        // In Safari, there's no way to write a (natively) focusable element into the page, and
        // exclude it from the page's tab order.
        // In this case just don't write the focusProxy into the DOM at all, and we'll deal
        // with virtual ISC focus only.
        // Note that this means you can't have a focusable widget with a working accessKey and
        // tabIndex -1 in Safari.
        return;
    }
    
    // Size the focus proxy to match the canvas unless we're in Safari
    
    var width = (isc.Browser.isSafari ? 1 : this.getViewportWidth()),
        height = (isc.Browser.isSafari ? 1 : this.getViewportHeight());
    
    var focusProxyString = isc.Canvas.getFocusProxyString(
                            this.getCanvasName(),
                            true,
                            this.getOffsetLeft() - 1, this.getOffsetTop() -1, 
                            width, height,
                            this.isVisible(), this._canFocus(),
                            tabIndex, this.accessKey,
                            // this param determines whether the element should handle events
                            // directly, or allow page level EH handling.
                            false,
                            // returns a string causing the ISC level focus to be updated
                            this._getNativeFocusHandlerString(), 
                            this._getNativeBlurHandlerString()
        );

    // Insert the focusProxyParent into the DOM in the same scope as the widget's clip handle.
    // Note: we insert AFTER not before the clip handle because redraw (for the special case
    // where we're allowing both children and content) makes the assertion that there's nothing
    // between the canvas's start tag and it's first child's start tag except for 'innerHTML'
    // type content. (If this was the first child of some widget and we inserted the
    // focusProxyParent before the widget's handle, we'd be writing the focus proxy between
    // this widget's start tag and the end of the parent's innerHTML)
    isc.Element.insertAdjacentHTML(this.getClipHandle(), "afterEnd", focusProxyString)
 
    // For simplicity, hang a flag on the widget saying that it has a focusProxy already.
    // Saves us having to get the F.P. from the DOM to check if it's written out already
    this._hasFocusProxy = true;
},

delayedMakeFocusProxy : function () {
    this.__delayedMakeFocusProxy = null;
    this.makeFocusProxy();
},

//>	@method	Canvas._clearFocusProxy()	(IA)
//		@group	focus
//          Internal Method to clear this widget's "focusProxy" from the DOM.
//<
_clearFocusProxy : function () {

    if (!this._useFocusProxy) return;

    // If there's a pending event to make the focus proxy, clear that out.
    if (this.__delayedMakeFocusProxy != null) {
        isc.Page.clearEvent("load", this.__delayedMakeFocusProxy);
        this.__delayedMakeFocusProxy = null;
    }
    
    // If we never create focusProxy, bail
    if (!this._hasFocusProxy) return;

    var fpp = this._getFocusProxyParentHandle();
    if (fpp != null) {

        // Note: focusProxyParentHandle has no pointers to this widget (if it did we should clear 
        // them now)
        if (isc.Browser.isDOM) {
            // remove object tree from the DOM.
            if (fpp.parentNode) {
                fpp.parentNode.removeChild(fpp);
            } else {
                this.logWarn("Unable clear focusProxy for this widget - element has no parentNode.");
            }

        } 

        // and clear the '_focusProxy' property from this widget
        this._focusProxy = null;	

    }
    // Clear out our helper '_hasFocusProxy' flag so makeFocusProxy doesn't NoOp in the future.
    this._hasFocusProxy = null;
    
},
//<FocusProxy


_useAccessKeyProxy : function () {
    return (isc.Browser.isMoz && this._useNativeTabIndex);
},
_makeAccessKeyProxy : function () {
    var accessKey = this.accessKey;
    if (!accessKey || !this.isDrawn() || !this._canFocus()) return;
    var handleName = this._getDOMID("focusProxy");
    var proxyString = isc.StringBuffer.concat(
        "<a id='", handleName, 
        "' onfocus='var _0=window.", this.getID(), ";if(_0){_0.focus();}' ",
        "accessKey='" + accessKey + "'></a>");
           
    isc.Element.insertAdjacentHTML(this.getClipHandle(), "beforeEnd", proxyString);
    this._accessKeyProxy = isc.Element.get(handleName);
},

_clearAccessKeyProxy : function () {
    var element = this._accessKeyProxy;
    delete this._accessKeyProxy;
    if (element) isc.Element.clear(element);
},


// Drawing children and peers
// --------------------------------------------------------------------------------------------

//>	@method	canvas.drawChildren()	(A)
//			Draw all children of this Canvas
//		@group	drawing
//
//		@param	document
//<
_$initial_draw : "initial draw",
drawChildren : function () {
	
	// if no children defined, simply return true
	if (this.children == null) return true;

	// drawChildren is only safe to call BEFORE this canvas has been drawn
	if (this.isDrawn()) {
		//>DEBUG
		this.logWarn("drawChildren() is only safe to call BEFORE a canvas has been drawn" +
                     this.getStackTrace());
		//<DEBUG
		return;
	}

	//>DEBUG
	if (this.children && this.logIsInfoEnabled(this._$drawing)) {
        this.logInfo("drawChildren(): " + this.children.length + " children", this._$drawing);
    }
	//<DEBUG

	// make sure that everything in the children array is a Canvas, and has us as its parentElement
    this._instantiateChildren();

    // NOTE: this entrypoint needs to be exactly here, because this moment - where the parent's
    // HTML exists in the DOM but no children have been drawn - is the only time you could draw some
    // children before deciding on the size of other children.  Otherwise you'd have to resize the
    // other children after drawing them, potentially causing Canvas redraws/native repaints
    this.layoutChildren(this._$initial_draw);
	
    if (this.manageChildOverflow) this._suppressOverflow = true;

	// draw all children (unless they have a masterElement, in which case the master will draw them
    // itself)
	for ( var i = 0; i < this.children.length; i++) {
		var child = this.children[i];

		// if the child has a masterElement, it's a peer of another child
		//	the other child will handle drawing it, so skip the draw here.
		if (child.masterElement) continue;

		// NOTE: the only legitimate way in which this child might already have been drawn 
        // is via a custom override of layoutChildren() above.  Otherwise all children should be
        // undrawn, since: 
        // - everything in the this.children array has gone through addChild(), hence was clear()d
        //   if it drew in another context
        // - this Canvas has never drawn it's children
        // - we don't allow Canvii that have undrawn parents to draw()
        // - we're skipping elements that have been drawn as a peer
        if (!child.isDrawn()) child.draw();
	}

},


_$parentDrawn:"parentDrawn",
_completeChildOverflow : function (children) {
    if (!this.manageChildOverflow) return;

    this._suppressOverflow = null;
 
    this._browserDoneDrawing(); // allows for easier timing
    //this.getHandle().scrollHeight;

    var count = 0;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (child != null && child._deferredOverflow) {
            count++;
            child._deferredOverflow = null;
            child.adjustOverflow(this._$parentDrawn);
        }
    }
    //if (count > 0) this.logWarn("completed child overflow for " + count + " children");
},


//>	@method	canvas.predrawPeers()	(A)
//			Draw all peers of this Canvas marked for pre-drawing
//		@group	drawing
//<
predrawPeers : function () {
    if (!this.peers) return;

    for (var i =0; i < this.peers.getLength(); i++) {
        var peer = this.peers[i];
        if (peer._drawBeforeMaster == true) {

    		// if the peer is not a canvas, or doesn't recognize us as its master
    		// call addPeer() to create it and/or add it to our list of peers
    		if (!isc.isA.Canvas(peer) || peer.masterElement != this) {
                this.peers.remove(peer);
    			this.addPeer(peer);
    		}
            
            peer.draw();
        }
    }
},

//>	@method	canvas.drawPeers()	(A)
//			Draw all peers of this Canvas
//		@group	drawing
//<
drawPeers : function () {
	// if no peers defined, simply return true
	if (!this.peers) return true;

	//>DEBUG
    if (this.logIsInfoEnabled(this._$drawing)) {
    	this.logInfo("drawPeers(): " + this.peers.length + " peers", "drawing");
    }
	//<DEBUG
    
	// go in two passes through the peers array
	//	 1) make sure that everything in there is a canvas, and has us as its masterElement
	//			if either is not true, call addPeer() to add it as a proper child
	//	 2) draw everything that hasn't been drawn already

	// rebuild the peers array to ensure that it contains real canvases, and each one
    // has us as its master element
	var oldPeers = this.peers;
	this.peers = [];

	for (var i = 0, peer; i < oldPeers.length; i++) {
		peer = oldPeers[i];
		
		// if the peer is not a canvas, or doesn't recognize us as its master
		// call addPeer() to create it and/or add it to our list of peers
		if (!isc.isA.Canvas(peer) || peer.masterElement != this) {
			this.addPeer(peer);

		// otherwise, it's already been set up correctly (by a previous call to addPeer())
		//	so we'll just add it back to our peers array (which we cleared out earlier)
		} else {
			this.peers.add(peer);
		}
	}
	
	// pass 2 -- draw all peers 
    // All peers now recognize this as the masterElement - so it can safely handle their drawing
    // (Even if they have a parent element, that will recognize that the peer has a master element 
    //  and cede drawing to the masterElement)
	for (i = 0; i < this.peers.length; i++) {
		var peer = this.peers[i];
       
		// set the peers position if snapTo or snapEdge are set
		if (peer.snapTo || peer.snapEdge) peer._resolvePercentageSize();
        // if the peer is not already drawn, draw it
        if (!peer.isDrawn()) peer.draw();
	}
    
},


//>	@method	canvas._insertHTML()	(A)
//			Internal routine to insert the HTML for this canvas AFTER the page has loaded
//			
//		@group	drawing
//
//		@return	(canvas)	Pointer to this canvas.  Returned so statements like the following will work:
//								var myCanvas = Canvas.newInstance({...}).draw()
//
//<
_insertHTML : function (includeInnerHTML) {
	
    // mark that we've starting drawing
    this.setDrawnState(isc.Canvas.DRAWING_HANDLE);
    
    var innerHTML = includeInnerHTML ? this._getInnerHTML() : null,

    
        buffer = this.getTagStart(true),
        gotArray = isc.isAn.Array(buffer),
        HTML;

    if (gotArray) {
        var origLength = buffer.length;

        buffer[buffer.length] = innerHTML;
        buffer[buffer.length] = this.getTagEnd();
        HTML = buffer.join(isc._emptyString);
        buffer.length = origLength;
    } else {
        HTML = isc.SB.concat(buffer, innerHTML, this.getTagEnd());
    }

    var newElement;
    var logEnabled = this.logIsInfoEnabled(this._$drawing);
    // if a specific DOM insertion position was specified
    var drawContext = this.drawContext;
    if (drawContext) {
        var element = drawContext.element,
            position = drawContext.position || "beforeBegin";
		//>DEBUG
        this.logInfo("_insertHTML(): drawing with " + position + 
                     " relative to element: " + this.echoLeaf(element), "drawing");
        //<DEBUG
        
        if (position == "replace") {
            // insert before, then remove
            position = "beforeBegin";
            if (isc.isA.String(element)) element = isc.Element.get(element); 
            newElement = this._insertAdjacentHTML(element, position, HTML, true);
            element.parentNode.removeChild(element);

            // drop drawContext (and htmlElement [same reason]);
            this.drawContext = null;
            if (this.htmlElement) this.htmlElement = null;
        } else {
            newElement = this._insertAdjacentHTML(element, position, HTML, true);
        }
        
    // if there's a drawn master Canvas, draw next to it
    // Note: peer vs master draw order
    // There are cases where we wish to draw a peer before we draw a master element into the
    // DOM - the specific example is the StretchImgButton, where in order to make the button
    // images auto-size to an overflow:visible label, we need to first draw the label out and
    // determine its drawn size before calling the 'getInnerHTML()' method.
    // In order to handle these cases we've introduced the concept of marking some peers for
    // 'predrawing' - which forces them to be written out before their masters (when draw() is
    // called on the master widget). 
    // TODO: to prevent accidental out of order drawing, log an error if a peer draws before
    // its master and hasn't been explicitly marked for predrawing.
    
    } else if (this.masterElement && (this.masterElement.getClipHandle() != null)) {
        //>DEBUG
        if (logEnabled) {
            this.logInfo("inserting HTML next to master element: " + this.masterElement, 
                         "drawing");
        } //<DEBUG

        // insert our HTML next to our masterElement in the document flow
        var master = this.masterElement.getClipHandle();
    	newElement = this._insertAdjacentHTML(master, this._$afterEnd, HTML, true);

    // if there's a parent Canvas        
    } else if (this.parentElement) {

        //>DEBUG
        if (logEnabled) {
            this.logInfo("inserting HTML into parent: " + this.parentElement,
                         "drawing");
        } //<DEBUG
        // insert our HTML within our parent's HTML
        var parent = this.parentElement.getHandle();
    	newElement = this._insertAdjacentHTML(parent, this._$beforeEnd, HTML, true);
      
    } else {
        // there is no parent Canvas, this is a top-level (absolute) Canvas.  Insert the HTML at
        // the end of the <BODY> tag to create a top-level element
    
        //>DEBUG
        if (logEnabled) {
            this.logDebug("inserting HTML at top level", "drawing");
        } //<DEBUG
        newElement = this._createAbsoluteElement(HTML);
    }	
    if (!(isc.Browser.isIE || isc.Browser.isOpera)) {
        
        if (newElement != null) {
            if (this.useClipDiv) {
                //this.logWarn("caching handle: " + this.echoLeaf(newElement));
                this._clipDiv = newElement;
                this._handle = newElement.firstChild;
            } else {
                this._handle = newElement;
            }
        } else {
            
        }
    }

    this.setDrawnState(isc.Canvas.HANDLE_DRAWN);
},


_createAbsoluteElement : function (html) {
    return this.ns.Element.createAbsoluteElement(html);
},
_insertAdjacentHTML : function (element, position, html, singleElement) {
    return this.ns.Element.insertAdjacentHTML(element, position, html, singleElement);
},

// _completeHTMLInit() : helper method for draw() and _insertHTML()
//
// Finishes up the drawing process
//
//  - Assumes we have already drawn the handle into the DOM, and called this.drawChildren()
//
//  - Sets up events
//  - calls this.drawPeers()
//  - marks as drawn and not dirty
//  - calls adjustOverflow
_completeHTMLInit : function () {
	
    // opportunity to modify content before overflow is adjusted
    this.modifyContent();

    // Moz strict / transitional mode requires explicit sizing of iframe if present
    // (100%/100% not respected)
    if (isc.Browser.isMoz && isc.Browser.isStrict && this.containsIFrame()) this._sizeIFrame();

    if (isc.screenReader) this.addContentRoles();

    
    if (this.manageChildOverflow && this.children != null) {
        this._completeChildOverflow(this.children);
    }

	// set up the handle for the canvas
	this.setUpEvents();

    // If a resize was attempted while the handle was being written out, resize the actual 
    // handle
    if (this._resizeHandleOnDrawComplete) {
        // actually resize the handle by calling _setHandleRect
        this._setHandleRect(this.left, this.top, this.width, this._height);
        // if we have a clip region set, it will have been clobbered by _setHandleRect.
        // restore it:
        // Note: already modified this._clip
        var clip = this._clip;
        if (isc.isAn.Array(clip)) this.setClip(clip);
        
        // AdjustOverflow will get called below
    }
    
	// mark that we've been drawn successfully and that we're not dirty
    this.setDrawnState(isc.Canvas.COMPLETE);
	this._dirty = false;

    // If we don't have a parentElement, add to the list of top level canvii
    if (this.parentElement == null) isc.Canvas._addToTopLevelCanvasList(this);

	// adjust according to our overflow property 
    
    if (this.parentElement != null && this.parentElement._suppressOverflow) {
        this._deferredOverflow = true;
    } else {
        this.adjustOverflow(this._$draw);
    }

    if (isc.screenReader) this.addPrimaryRole();

	// if we have any peers defined, draw them now
    
	this.drawPeers();
},

//> @method canvas.setHtmlElement() 
// Setter for the +link{canvas.htmlElement}.
// @param element (DOM element) New htmlElement for this canvas, or Null to clear the existing
//      htmlElement
// @group htmlElement
// @visibility external
//<
setHtmlElement : function (element) {
    if (this.htmlElement == element) return;
    this.htmlElement = element;
    if (!this.htmlPosition) this.htmlPosition = "afterBegin";
    var context = element ? {position:this.htmlPosition, element:this.htmlElement}  : null;
    // setDrawContext will handle clearing / drawing / etc.
    this.setDrawContext(context);
},

//> @method canvas.setHtmlPosition() 
// Setter for the +link{canvas.htmlPosition}.
// @param element (DrawPosition) New htmlPosition for this canvas
// @group htmlElement
// @visibility external
//<
setHtmlPosition : function (position) {
    if (position == null) position = "afterBegin";
    if (this.htmlPosition == position) return;

    this.htmlPosition = position;
    if (this.htmlElement == null) return;    
    var context = {position:this.htmlPosition, element:this.htmlElement};

    // setDrawContext will handle clearing / drawing / etc.
    this.setDrawContext(context);

},

// Redrawing
// --------------------------------------------------------------------------------------------

//>	@method	canvas.isDirty()	(A)
// Returns whether a canvas is waiting to be redrawn. Will return true if 
// +link{canvas.markForRedraw()} has been called, but this canvas has not yet been
// redrawn.
// @group drawing
// @return (boolean) true is this canvas needs to be redrawn; false otherwise
// @visibility external
//<
isDirty : function () {
	return this._dirty == true;
},


//>	@method	canvas.markForRedraw()  ([])
// Marks the widget as "dirty" so that it will be added to a queue for redraw. Redraw of dirty
// components is handled by a looping timer and will after a very short delay (typically less than
// 100ms). In most cases it is recommended that developers use <code>markForRedraw()</code>
// instead of calling +link{canvas.redraw()} directly. Since this method queues the redraw, multiple
// calls to markForRedraw() within a single thread of excecution will only lead to a single DOM
// manipulation which greatly improves application performance.
//
//  @visibility external
//  @group  drawing
//  @param  [reason]    (string : "no reason provided") reason for performing the redraw
//<
markForRedraw : function (reason) {

    if (isc._traceMarkers) arguments.__this = this;

	// If we've been drawn already, add this to the queue of items to be redrawn automatically.
    // If we're already dirty, we're already in the redraw list.
	if (this.isDrawn() && !this.isDirty()) {
        this._logRedraw(reason);
		isc.Canvas.scheduleRedraw(this);

	    // mark the item as dirty so we don't schedule the event again later
	    this._dirty = true;
	}
},

readyToRedraw : function (reason, askedToRedraw) {
    if (isc._traceMarkers) arguments.__this = this;

    if (!this.isDrawn()) {
    	
        return false;
    }

    // NOTE: unsafe times to redraw
    // When we redraw a widget, we throw away the old HTML and replace it with new HTML - a
    // subtle consequence of this is that the DOM object that is the target of the current event
    // is *destroyed*.  Various browsers on various platforms while processing particular types
    // of events with various HTML contents react badly to this.
    //
    // So now, whenever there is an attempt to redraw during mouseDown or mouseUp, we delay the
    // redraw regardless of the browser, since we would prefer not to learn about the rest of
    // the obscure cases where an immediate redraw won't work.
    //
    // However, postponing this redraw means that this redraw, which the caller wanted to happen
    // immediately, will be done in a batch of redraws, which means no actual native repaint
    // will occur until every widget that needs to redraw is done redrawing, which might take a
    // while.  So we set a "priority redraw" flag so the redraw of this object is done in a
    // batch of its own.
    
    var EH = this.ns.EH;
    if ((EH._handlingMouseUp || EH._handlingMouseDown) && EH.lastTarget == this) {
        if (askedToRedraw) {
            // if actually asked to redraw, schedule for later
            this._logRedraw(reason, true);
            this.priorityRedraw = true;
            // Mark as non-dirty (otherwise markForRedraw is a no-op)
            this._dirty = false;
            this.markForRedraw(false);
        }

        return false;
    }

    return true;
},

_logRedraw : function (reason, type) {
    //>DEBUG

    // NOTE: some callers pass reason = false to suppress this log, since the redraw is already
    // logged elsewhere (clearRedrawQueue and redrawChildren) 
    if (reason == false || !this.logIsInfoEnabled(this._$redraws)) return;

    // log a stack if 'redrawTrace' log is enabled, or in debug mode and no reason was provided
    var logTrace = (!reason && this.logIsDebugEnabled(this._$redraws) ||
                    this.logIsDebugEnabled("redrawTrace"));

    var message;
    if (type == null) message = "Scheduling redraw ";
    else message = (type == true ? "DEFERRED " : "") + "Immediate redraw ";

    this.logInfo(message +
                 // log that widget is dirty only if an immediate redraw was requested (whether
                 // it was deferred or not)
                 (this.isDirty() && type != null ? "of dirty widget " : "") +
                 // redrawing with children indicates all children redraw
                 (this.children && this.children.length > 0 ? 
                  "(" + this.getChildCount() + " children) " : "") +
                 // reason for redraw 
                 "(" + (reason ? reason : "no reason provided") + ")" +
                 // log trace
                 (logTrace ? this.getStackTrace() : ""), 
                 this._$redraws);
    //<DEBUG
},

//>	@method	canvas.redraw() ([A])
// Redraws the widget immediately with its current property values.  
//
// Generally, if you want a Canvas to redraw, call markForRedraw() - this will cause the Canvas to
// be redrawn when current processing ends, so that a series of modifications made to a Canvas will
// cause it to redraw only once.
//
// Only call redraw() directly if you need immediate responsiveness, for example you are redrawing
// in response to continuous mouse motion.
//
//  @visibility external
//  @group  drawing
//  @param  [reason]    (string : "no reason provided") reason for performing the redraw
//<
//
// NOTE: this does not necessarily have to redraw all of the HTML of a Canvas.  Subclasses can
// override redraw to do something smarter.
redraw : function (reason) {
    if (isc._traceMarkers) arguments.__this = this;

    if (!this.readyToRedraw(reason, true)) return this;

    //>DEBUG
    this._logRedraw(reason, false);
    // track total redraws
    this._addStat(this._$redraws);

    
    //<DEBUG

    var start = isc.timeStamp();
    

    this._updateHTML();
    

    // track last redraw time
    this._lastRedrawTime = isc.timeStamp() - start;

	return this;
},

redrawIfDirty : function (reason) {
    if (this.isDrawn() && this.isDirty()) return this.redraw(reason);
},

//>	@method	canvas._updateHTML()	(A)
//		Redraw an existing layer by generating new HTML and replacing the existing HTML.  
//
//      NOTE: non-framework code should call redraw(), not this method
//
//		@group	drawing
//
//		@return	(canvas)	Pointer to this canvas.  Returned so statements like the following will
//		                    work:
//								var myCanvas = Canvas.newInstance({...}).draw()
//<
_updateHTML : function () {

    //>DEBUG
    var logDebugEnabled = this.logIsDebugEnabled(this._$drawing),
        logInfoEnabled = this.logIsInfoEnabled(this._$drawing),
        startTime;
    if (logDebugEnabled) startTime = isc.timeStamp();
    if (logInfoEnabled) this.logInfo("_updateHTML(): redrawing", "drawing");
    //<DEBUG

    // if we have any peers, call the redrawPredrawnPeers() method to check for any peers marked
    // for drawing before the master element, and redraw them first.
    // (See addPeer() and predrawPeers() methods for more info).
    if (this.peers != null && this.peers.getLength() > 0) this.redrawPredrawnPeers();

    var hasChildren = this.children && this.children.length > 0,
        childrenAndContent = this.allowContentAndChildren && hasChildren;
    
    // special case: if shouldRedrawOnResize() assume redraw should always redraw content - if
    // we have children, flip on 'allowChildrenAndContent'
    
    if (hasChildren && !childrenAndContent && this.shouldRedrawOnResize()) {
        childrenAndContent = true;
    }

    
    if ((!hasChildren || childrenAndContent) &&
        (this.getVisibleWidth() > this.getWidth() ||
         this.getVisibleHeight() > this.getHeight()))
    {
        //this.logWarn("resizing overflow Canvas before redraw");
        this._setHandleRect(null, null, this.width, this._height);
    }

    if (hasChildren) {
        // update the HTML that came from parent.getInnerHTML(). 
        if (childrenAndContent) this._updateParentHTML();

        this.redrawChildren();
    } else {	
        // childless - update inner HTML
        this._updateInnerHTML();
    }
    
    // If we're writing out a placeholder div to enforce scroll-size, and we just
    // redraw our innerHTML, reapply it now.
    
    if (this._enforcingScrollSize && !hasChildren) {
        // The old div will no longer be present, so if this._scrollSizeDiv (if set)
        // will be out of date (pointing to an element that's no longer written into the DOM).
        // Clear the pointer out to ensure we create a fresh div.
        delete this._scrollSizeDiv;
        this.enforceScrollSize(this._enforcingScrollSize[0], this._enforcingScrollSize[1]);
    }
    
    // opportunity to modify new content before overflow is adjusted
    this.modifyContent();
    
    if (isc.screenReader) this.addContentRoles();

    // set up events in the handle
    this.setUpEvents();

    // mark this item as not dirty any more
    
    this._dirty = false;		
    // adjust the overflow again
    this.adjustOverflow(this._$redraw, null, true);

    // if we have any peers, redraw them
    
    this.redrawPeers();

    
    //>DEBUG
    if (logDebugEnabled) {
        this.logDebug("Redraw() - Total time to redraw in DOM:" + (isc.timeStamp() - startTime),
                      "drawing");
    }
    //<DEBUG
    
    // return "this" for chaining calls (canvas.redraw().moveTo(..).. )
    return this;
},

// update the HTML of a parent without changing the HTML of its children

_updateParentHTML : function () {
    var innerHTML = this._getInnerHTML(),
        thisHandle = this.getHandle();

    // We achieve this by removing all the text inside the content handle for the
    // widget up to the handle of the first child, and then inserting the new innerHTML
    // in the same place
    //
    // Note: within the content handle for a canvas, we will always have the innerHTML
    // (returned from getInnerHTML()) for the canvas, followed by the child nodes.
    // Therefore everything up to the first child's handle is the parents content (this
    // assumption could only be violated by unsupported manual DOM manipulation).

    

    // remove everything up to the first Canvas.  NOTE: we can't compare against
    // this.children[0].getHandle() because our children may draw out of order, get clear()d,
    // etc.
    while (thisHandle.hasChildNodes()) {
        var eventProxy = thisHandle.firstChild.getAttribute ?        
                         thisHandle.firstChild.getAttribute(this._$eventProxy) : null;
        if (eventProxy && isc.isA.Canvas(window[eventProxy])) break;
        //this.logWarn("removing element: " + this.echoLeaf(thisHandle.firstChild));
        thisHandle.removeChild(thisHandle.firstChild);
    }
    // add the parent's new HTML 
    isc.Element.insertAdjacentHTML(thisHandle, this._$afterBegin, innerHTML);
},
_$eventProxy : "eventProxy",

// update the innerHTML of a childless Canvas
_updateInnerHTML : function () {
    var wasPrinting = this.isPrinting;    
    this.isPrinting = false;
	var innerHTML = this._getInnerHTML();
    

    // NOTE: Moz also supports assigning to innerHTML, but this is about 10% faster
    if (isc.Browser.isMoz) {

        var element = this.getHandle();

        // wipe out the current contents
        while (element.hasChildNodes()) element.removeChild(element.firstChild);

        var range = element.ownerDocument.createRange();
        // select all of this element's contents (which is empty)
        range.selectNodeContents(element);
        // and collapse the range to an insertion point, which is where we'll put the new
        // HTML.  
        
        range.collapse(true);
 
        // create new contents of the element
        var fragment = range.createContextualFragment(innerHTML);
        element.appendChild(fragment);

    } else {
        this.getHandle().innerHTML = innerHTML;
    }
    this.isPrinting = wasPrinting;
},

// opportunity to modify drawn or redrawn content before overflow is adjusted
modifyContent : function () {},

//>	@method	canvas.redrawChildren()	(A)
//		Redraw all of our children
//		@group	drawing
//<
redrawChildren : function () {
	// if no children defined, simply return true
	if (! this.children) return true;

	//>DEBUG
    this.logInfo("redrawChildren(): " + this.children.length + " children", "drawing");
	//<DEBUG

	// redraw each child
	for (var list = this.children, i = 0; i < list.length; i++) {
		var child = list[i];

		if (!isc.isA.Canvas(child)) continue;
		if (child._redrawWithParent) {            
			child.redraw(false);
		}
	}
},

//>	@method	canvas.redrawPredrawnPeers()	(A)
//		Redraw any of our peers marked for preDraw via the '_drawBeforeMaster' flag
//		@group	drawing
//      @see    predrawPeers()
//      @see    addPeer()
//<
redrawPredrawnPeers : function () {
    // bail if we have no peers.
    if (!this.peers || this.peers.getLength < 1) return;
    
	// draw each peer marked for predrawing
	for (var list = this.peers, i = 0; i < list.length; i++) {
		if (list[i] && list[i]._redrawWithMaster && list[i]._drawBeforeMaster) {
			list[i].redraw("redrawPeers");
		}
	}
    
},


//>	@method	canvas.redrawPeers()	(A)
//      Redraw all of our peers (excluding those marked for drawing / redrawing before their 
//      master element)
//      @group	drawing
//<
redrawPeers : function () {
	// if no peers defined, simply return true
	if (!this.peers) return true;

	//>DEBUG
    this.logInfo("redrawPeers(): " + this.peers.length + " peers", "drawing");
	//<DEBUG

	// redraw each peer
	for (var list = this.peers, i = 0; i < list.length; i++) {
		if (list[i] && list[i]._redrawWithMaster && !list[i]._drawBeforeMaster) {
			list[i].redraw("redrawPeers");
		}
	}
},

// Update / Refresh / Replace from server
// --------------------------------------------------------------------------------------------

//>	@method	canvas.updateFromServer()	(A)
//
// A flexible way to update a component from the server.
// <p>
// Makes a request to the server at the URL specified by the actionURL of the provided RPCRequest.
// Sets +link{attr:RPCRequest.evalResult} and +link{attr:RPCRequest.supporessAutoDraw} to true in
// the provided request and automatically makes available the component on which this method is
// called under the name 'targetComponent' in the response received from the server (i.e. in the
// +link{attr:RPCRequest.evalVars} of the request).
// <p>
// For example, let's say you wanted to add a component available from the server at the URL
// '/myComponent.jsp' to a layout on the current page called 'myLayout'.  You can accomplish this by
// calling this on the client:
// <pre>
// myLayout.updateFromServer({actionURL: "/myComponent.jsp"});
// </pre>
// In the body of myComponent.jsp you could then do e.g:
// <pre>
// var newComponent = Label.create({contents: "hello world"});
// targetComponent.addMember(newComponent);
// </pre>
// The URL targeted by updateFromServer must produce valid JavaScript code, but how that happens is
// up to you - this can be a static file, a JSP or a Servlet.
// <p>
// Note that you can use all other features of +link{class:RPCRequest} as part of
// updateFromServer().  For example, if you wanted show a prompt with the contents "loading
// component" while the update is in progress and get a callback when it completes while also
// sending some parameters to the server that would be available via request.getParameter() in
// e.g. your JSP, you can issue the above request as follows:
// <pre>
// myLayout.updateFromServer({
//     actionURL: "/myComponent.jsp",
//     showPrompt: true,
//     prompt: "loading component",
//     params : {
//         "componentId": myLayout.getID(),
//         "foo": "bar"
//     },
//     callback: "alert('done updating "+myLayout.getID()+"')"
// });
// </pre>
//
// @param rpcRequest (RPCRequest) minimally must specify the actionURL, but all other RPCRequest
//                                features are available.
//
// @visibility internal
//<
updateFromServer : function(request) {
    // make a copy so we don't modify user's object
    request = isc.clone(request);
    isc.addProperties(request, {
        
        useXmlHttpRequest: true,
        evalResult : true,
        suppressAutoDraw : true
    });

    // this component is autogically made available as part of the evalVars
    if(!request.evalVars) request.evalVars = {};
    if(!request.evalVars.targetComponent) request.evalVars.targetComponent = this;

    isc.rpc.sendRequest(request);
},


refreshFromServer : function(url, data, prompt, callback) {
	this._refreshOrReplaceFromURL("refresh", url, data, prompt, callback);
},

replaceFromServer : function(url, data, prompt, callback) {
	this._refreshOrReplaceFromURL("replace", url, data, prompt, callback);
},
		

		
_refreshOrReplaceFromURL : function(action, url, data, prompt, callback) {
    // don't more than one attempted refresh/replace, because we don't want to write a bunch of
    // logic to guarantee the correct order (i.e. server may respond to the second request before
    // the first)
	if (this._refreshing) {
        this.logWarn("Attempt to "+action+" while "+this._refreshAction+" is in progress - ignoring.");
        return;
    }
    this._refreshing = true;
	this._refreshAction = action;
    this._refreshCallback = callback;

    

	this.logDebug("Submitting to " + action + " URL: " + url + ", with data: " + this.echo(data));

    isc.Comm.sendFieldsToServer({
        URL:url,
        fields:data,
        prompt:prompt,
        callback: this.getID() + "._refreshReply(frame)",
        resultVarName: this.refreshVariable
    });
},

_refreshReply : function (frame) {
    // release the refresh lock
    this._refreshing = false;

	var action = this._refreshAction;
    var newConfig = frame[this.refreshVariable];

    if (!isc.isAn.Object(newConfig)) {
        this.logError("Expected object literal for " + action + 
                      ", but got: " + isc.Log.echo(newConfig));
        return;
    }
    // need to clone the newConfig since it came from another frame
    newConfig = isc.clone(newConfig);

    var visibleInstance = this;
	// if it's a refresh just setProperties on the existing object
	if (action == "refresh") this.setProperties(newConfig);
	else { // it's a replace
		// if a constructor property was not passed in for the item that we're replacing this Canvas 
		// with, assume it's going to be of the same type as whatever it is replacing.
		if (!newConfig._constructor) newConfig._constructor = this.getClassName();
		visibleInstance = this.replaceWith(newConfig);
	}

    // possibly call after callback?
	isc.clearPrompt();

    // call the registered callback, if any
    if (this._refreshCallback) {
        if (!isc.isA.Function(this._refreshCallback)) { 
            this._refreshCallback = isc.Func.expressionToFunction("canvas", this._refreshCallback);
        }
        // make sure the conversion worked
        if (!isc.isA.Function(this._refreshCallback)) {
            this.logError("Can't convert "+action+" callback '"+this._refreshCallback
                          +" to a function - not firing callback!");
            return;
        }
        this._refreshCallback(visibleInstance);
    }    
        
},

// Clear and Destroy
// --------------------------------------------------------------------------------------------

//>	@method	canvas.clear() [A]
// Remove all visual representation of a Canvas.
// <P>
// This is far more expensive than hide(), because in order to become visible again, the Canvas must
// be draw()n again.  Generally, application code has no reason to call clear(); if you want to
// temporarily hide a Canvas, use hide() and show(), and if you want to permanently destroy a
// Canvas, use destroy().
// <P>
// Note: a clear() will happen as part of moving a Canvas to a different parent.  See addChild().
//
// @see addChild()
//
// @visibility external
//<
clear : function (dontReport) {
    this._clearing = true;

	//>DEBUG
    // increment total clears (if not called from parent or as part of destroy)
    if (!dontReport && this.logIsInfoEnabled("clears")) {
        var message = "clear()" +
                      // clearing with children indicates all children clear (without
                      // individually reporting it)
                      (this.children && this.children.length > 0 ? 
                       " (" + this.getChildCount() + " children) " : "") +
                      (this.logIsDebugEnabled("clears") ? this.getStackTrace() : "");

        // NOTE: in the log, we only report the first call to clear(), but for the stat, we
        // report each individual handle clear (from clearHandle())
        this.logInfo(message, "clears");

        
    }
	//<DEBUG

    // blur - don't let undrawn items have focus
    this._updateFocusForHide();
 
	// remove from EventHandler mask registry if necessary
	// NOTE: This must be called before we clear peers, since unregisterMaskableItem()
	//	will try to destroy the event mask, which is a peer.
	if (this._eventMask) this.ns.EH.unregisterMaskableItem(this);
	
    //>EditMode if we had the resize thumbs, hide them
    if (this == isc.Canvas._thumbTarget) isc.Canvas.hideResizeThumbs();
    //<EditMode

    
	// tell all of our children to clear so they clean up their own act
	if (this.children) {
		for (var list = this.children, i = 0; i < list.length; i++) {
			var child = list[i];
			if (!isc.isA.Canvas(child)) continue;
			child.clear(true);
		}
	}
    if (this.parentElement) this.parentElement.childCleared(this);
    if (this.masterElement) this.masterElement.peerCleared(this);
    
	// clear the handle for this widget
	if (this.getHandle()) this.clearHandle();
	
    //>FocusProxy If we have a focusProxy, clear it from the DOM as well.
    if (this._useFocusProxy) this._clearFocusProxy();
    //<FocusProxy
    
    // Clear the scroll-size enforcer div if present
    // (Don't call stopEnforcing - if we get drawn again, continue to enforce the scrollSize)
    delete this._scrollSizeDiv;
    
    // clear the pointer to the accessKeyProxy element, if there is one
    delete this._accessKeyProxy;
    
    
	
    // clear any delayed draw event
    if (this.deferredDrawEvent) {
        isc.Page.clearEvent(this.deferredDrawEvent);
        delete this.deferredDrawEvent;
    }
	
	// if we have any peers, clear them as well
	if (this.peers) {
		for (var list = this.peers, i = 0; i < list.length; i++) {
			list[i].clear(true);
		}
	}
	
	// note that we're no longer droppable
	if (this.canAcceptDrop) this.ns.EH.unregisterDroppableItem(this);

	// and note that we're no longer drawn
	this.setDrawnState(isc.Canvas.UNDRAWN);
    
    delete this._clearing;
},


//>	@method	canvas.destroy()	(A)
//  Permanently destroy a Canvas.<br><br>
//  
//  This does everything that clear() does, but unlike clear(), the Canvas cannot be draw()'n again,
//  cannot be referenced by it's global ID, and is eligible for garbage collection (assuming that
//  application code is not holding a reference to the Canvas).<br><br>
//
//  Any attempt to call a method on a destroyed Canvas will generally result in an error.  If your
//  application is forced to hold onto Canvas's that might be destroy()d without warning, you can
//  avoid errors by checking for the +link{canvas.destroyed} property.  If you override certain Canvas
//  methods, your code may be called while a Canvas is being destroy()d; in this case you can avoid
//  extra work (and possibly errors) by checking for the +link{canvas.destroying} property.
//  <P>
//  Note that <code>destroy()</code> should not be called directly in event handling code for this
//  canvas. For this reason, wherever possible we recommend using +link{canvas.markForDestroy()}
//  instead of calling this method directly. 
// 
// @see canvas.markForDestroy()
// @visibility external
//<

//> @attr   canvas.destroyed    (boolean : null : RA)
// If this property is set to <code>true</code>, the +link{canvas.destroy(), destroy()} method
// has been called on this canvas. This implies the canvas is no longer valid. Its ID has been
// removed from global scope, and calling standard canvas APIs on it is likely to result in 
// errors.
// @see canvas.destroy()
// @visibility external
//<

//>@attr    canvas.destroying   (boolean : null : RA)
// This property is set to true when the +link{Canvas.destroy()} method is called on a widget.
// If this property is true, but +link{Canvas.destroyed} is not, this indicates the canvas is
// in the process of being destroyed.
// @see canvas.destroy()
// @visibility external
//<


destroy : function (indirectDestroy) {
    // if we're marked doNotDestroy, just clear()
    if (this.doNotDestroy) {
        this.clear();
        return;
    }
    
    // if we're already destroyed don't do it again
    if (this.destroyed) return;

    // set a flag so we don't do unnecessary work during a destroy()
    this.destroying = true;
    // shouldn't need to blur, as both 'hide()' and 'clear()' do a blur, and if this isn't
    // drawn it won't have focus

    // clear the overflow timer for delayed adjustoverflow
    if (this._overflowQueued) isc.Timer.clearTimeout(this._overflowTimer);

    // if this widget is showing a clickMask (eg modal Dialog), get rid of it.  This will no-op
    // if this widget is not showing a clickMask.
    this.hideClickMask();

    //>DEBUG 
    if (!this._iscInternal) {
        // report every destroy for stats, but only log the direct destroys when 
        // parents/masters destroy children/peers.
        this._addStat("destroys"); 
        if (!indirectDestroy) {
            this.logInfo("destroy()" + 
                         (this.children && this.children.length > 0 ? 
                          " (" + this.getChildCount() + " children) " : "") +
                         (this.logIsDebugEnabled("destroys") ? this.getStackTrace() : ""),
                         "destroys");
        }
    } //<DEBUG

    // If we're showing the hover canvsa, clear it. 
    if (isc.Hover.lastHoverCanvas == this) isc.Hover.hide();
    

    // destroy our DOM representation.  
	this.clear(true);

	// tell all of our children to destroy so they clean up their own act
	if (this.children) {
		for (var list = this.children.duplicate(), i = 0; i < list.length; i++) {
			var child = list[i];
			if (!isc.isA.Canvas(child)) continue;
			child.destroy(true);
		}
	}

	// if we have any peers, destroy them as well
	if (this.peers) {
		for (var list = this.peers.duplicate(), i = 0; i < list.length; i++) {
			list[i].destroy(true);
		}
	}

	// wipe out our links to our children
	delete this.peers;
	delete this.children;
    
    // Verify that we have no scrollbars. May not have been caught in the peer class
    // if the scrollbar was never rendered out.
    if (this.hscrollbar && !this.hscrollbar.destroyed) {
        this.hscrollbar.destroy(true);
        delete this.hscrollbar;
    }
    if (this.vscrollbar && !this.vscrollbar.destroyed) {
        this.vscrollbar.destroy(true);
        delete this.vscrollbar;
    } 
    
    this.deparent();
    this.depeer();
    
    // if we have an event proxy, or any other widgets are event proxies for this one, clear
    // out the references in both directions.
    if (this.eventProxy != null) this.clearEventProxy();
    if (this._proxiers != null) {
        for (var list = this._proxiers.duplicate(), i = 0; i < list.length; i++) {
            list[i].clearEventProxy();
        }
    }
    
    if (this.locatorParent && this.locatorParent.locatorChildDestroyed) {
        this.locatorParent.locatorChildDestroyed(this);
    }
    delete this.locatorParent;

	// remove ourselves from the canvas list
	this._canvasList();
    
    // remove ourselves from the top-level canvas list
    isc.Canvas._removeFromTopLevelCanvasList(this);
    
    // remove ourselves from the tab-order management system
    this._removeFromAutoTabOrder();
    
    // notify the EH that we've been destroyed so it can clear up any pointers it has to us
    isc.EH.canvasDestroyed(this);
    
    // clear our global ID (removes the window.ID pointer to us)
    isc.ClassFactory.dereferenceGlobalID(this);
    
    // If we have been notified of anything pointing to this object, remove that pointer.
    
    if (this.pointersToThis != null) {
        for (var i = 0; i < this.pointersToThis.length; i++) {
            var pointer = this.pointersToThis[i];
            if (pointer.object && (pointer.object[pointer.property] == this)) {
                // NOTE: don't use 'delete', as this crashes on the global window object in IE
                var undef;
                pointer.object[pointer.property] = undef;
            }
        }
        // Clear up our pointers in the other direction.
        delete this.pointersToThis;
    }
    
    // delete all properties
    
    if (this._deletePropsOnDestroy) {
        for (var prop in this) {
            delete this[prop];
        }
    }

    // release the unique IDs we generated for our DOM element(s) so they can be reused by other
    // widgets
    
    this._releaseDOMIDs();
    
    // set a destroyed flag so that if someone still has a pointer to this widget, they can tell
    // it's destroyed
    this.destroyed = true;

    
},

//> @method canvas.markForDestroy()
// +link{canvas.destroy(),destroy()} this canvas on a timeout. This method should be used instead of
// calling <code>canvas.destroy()</code> directly unless there's a reason a the canvas needs to be
// destroyed synchronously. By using a timeout, this method ensures the <code>destroy()</code> will
// occur after the current thread of execution completes. This allows you to easily mark canvases
// for destruction while they're handling events, which must complete before the canvas can be
// destroyed.
// @see canvas.destroy()
// @visibility external
//<
markForDestroy : function () {

    if (isc._traceMarkers) arguments.__this = this;
    
    if (this.destroyed || this.destroying || this.isPendingDestroy()) return;
    this._pendingDestroy = true;

    if (this.logIsInfoEnabled("destroys")) {
        this.logInfo("markForDestroy() - setting up delayed destroy() call for this component.",
                    "destroys");
    }
    isc.Canvas.scheduleDestroy(this);
},

isPendingDestroy : function () {
    return !this.destroyed && !this.destroying && (this._pendingDestroy == true);
},


//>	@method	canvas.clearHandle()	(A)
//		Clear the canvas handle to free up memory (as much as we can anyway).
//		Note: this can assume that there actually is a handle.
//		@group	handles
//<
clearHandle : function () {
    // if we don't have a handle now, we've probably already been cleared...
    if (!this.getHandle()) return;
 
    // report each individual handle clear for the stat (only actual calls to clear() are
    // logged)
    this._addStat("clears");

    
    this.getHandle().eventProxy = null;
    this.getClipHandle().eventProxy = null;

    // If we have both a content element and a clip element, the clip element is the outer 
    // element, so it's the one to destroy
    var handle = this.getClipHandle();

    // and clear our ref's to the handle so we don't try to access it again
    this._handle = null;	
    this._styleHandle = null;	
    this._clipDiv = null;

    // use 'Element.clear()' to actually erase the handle from the DOM.
    
        

    
    isc.Element.clear(handle, this._clearWithRemoveChild);

},

// Replacing / Placing in the DOM
// --------------------------------------------------------------------------------------------

//>	@method	canvas.replaceWith()	(A)
//		Clear this canvas handle, and insert another canvas at the same place in the DOM
//      so if relatively positioned, the document will continue to flow around the replacement
//		@group	handles
//      @param  otherCanvas (widget)    Canvas with which to replace this one.
//<
replaceWith : function (otherCanvas) {
    // bail if passed something that isn't an object
    // (we'll accept either an object literal canvas init descriptor, or a canvas)
    if (!isc.isAn.Object(otherCanvas)) return;
    
    // -- Get all the information from 'this' we need:

    // In DOM browsers, for relatively positioned Canvii, place a marker in the DOM so that we
    // can put the replacing Canvas in exactly the same spot.  In Non-DOM browsers we can't do
    // a proper replace within the document flow instead we'll just clear this widget, and put
    // the replacement in the same parent.

    var marker;
    if (isc.Browser.isDOM) {
    
        // place a marker element in the DOM where this element was
        var ID = isc.ClassFactory.getNextGlobalID();
        isc.Element.insertAdjacentHTML(this.getClipHandle(), "afterEnd", 
                                     "<DIV ID=" + ID + "></DIV>");
        var marker = this.getDocument().getElementById(ID);

        // pass the marker element to the replacing Canvas to indicate the place where it should
        // draw
        otherCanvas.drawContext = { element:marker };
    }
    
    // the widget replacing us needs to become a child/peer of our parent/master if we have one
    var parentElement = this.parentElement,
        masterElement = this.masterElement,
        //if the original Canvas was in a Layout, preserve it's position.  
        
        inLayout = (isc.isA.Layout(parentElement) && parentElement.hasMember(this)),
        layoutPosition = (inLayout ? parentElement.getMemberNumber(this) : 0);

    // -- Completely remove this canvas 
    // Note: We're removing this canvas *before* initializing the other object into a canvas
    // (if it's not already a Canvas) to avoid issues with colliding IDs.
    this.destroy();

    // get rid of the other canvas handle (*if it existed already)
    if (isc.isA.Canvas(otherCanvas)) {
        otherCanvas.clear();
    // If it's just an init block, set autoDraw to false and initialize
    } else {
        otherCanvas.autoDraw = false;
        
        otherCanvas = isc.ClassFactory.newInstance(otherCanvas);
        if (otherCanvas == null) {
            //>DEBUG
            this.logWarn("canvas.replaceWith(): Unable to create a widget " +
                         "instance from the argument passed in.  Returning.")
            //<DEBUG
            return;
        }
    }

    // draw the replacing Canvas as a Layout member, child, peer, or top-level widget according
    // to the replaced Canvas
    if (inLayout) {
        parentElement.addMember(otherCanvas, layoutPosition)
    } else if (parentElement) {
        parentElement.addChild(otherCanvas)
    } else if (masterElement) {
        masterElement.addPeer(otherCanvas)
    }

    if (!otherCanvas.isDrawn()) otherCanvas.draw();

    if (isc.Browser.isDOM) {
        // Remove the marker so it doesn't clutter up the document
        if (marker.parentNode) {
            marker.parentNode.removeChild(marker)
        } else {
            //>DEBUG
            this.logWarn("unable to clear marker")
            //<DEBUG
        }
    }

    // return the instance that replaced us
    return otherCanvas;
},

//> @method canvas.setDrawContext() [A]
// Set the DOM location where this Canvas should draw.<P>
// If the Canvas has a Canvas parent, it will deparent().<P>
// If the Canvas is already drawn, it will draw in the new location.
//
// @param drawContext (DrawContext) DrawContext object indicating a DOM location.  Pass null
//                                  to clear the draw context and draw at top level.
// @group drawContext
// @visibility drawContext
//<
setDrawContext : function (drawContext) {
    var wasDrawn = this.isDrawn();

    // deparent if we have a parent, since we don't know if the drawContext is inside the
    // existing parent (we could check, but for now, having a drawContext is logically distinct
    // from having a Canvas parent)
    this.deparent();
    
    if (wasDrawn) this.clear();
    this.drawContext = drawContext;
    if (wasDrawn) this.draw();
},


// HTML for Canvas main tag 
// --------------------------------------------------------------------------------------------

// _getDOMID - helper to provide unique IDs for our DOM elements
// These DOM IDs are arbitrary strings provided by ClassFactory.getDOMID() which we write into
// the appropriate DOM sub elements we create.
// dontCache parameter: If passed we don't maintain a map from the partName to the generated ID -
// any cacheing of the generated ID by partName should be handled by the calling code.
_getDOMID : function (partName, dontCache, dontReuse) {
    // Allow callers to handle their own cacheing if desired
    if (dontCache) {
        var ID = isc.ClassFactory.getDOMID(this.getID(), partName);
        if (this.reuseDOMIDs) {
            if (!this._uncachedDOMIDs) this._uncachedDOMIDs = [];
            this._uncachedDOMIDs[this._uncachedDOMIDs.length] = ID;
        }
        return ID;
    }

    if (!this._domIDs) this._domIDs = {};
    if (!this._domIDs[partName]) 
    this._domIDs[partName] = isc.ClassFactory.getDOMID(this.getID(), partName);

    return this._domIDs[partName];
},
// helper to retrieve the part name based on DOM ID
_getDOMPartName : function (domID) {
    if (!this._domIDs) return null;
    // This is a reverse lookup. If performance becomes a concern we could maintain 
    // a reverse map instead...
    for (var ID in this._domIDs) {
        if (this._domIDs[ID] == domID) return ID;
    }
},
// reuseDOMIDs
// On destroy() we call ClassFactory.releaseDOMIDs() so the auto-generated DOM IDs may be reused
// within the page.
reuseDOMIDs:false,
_releaseDOMIDs : function () {
    if (!this.reuseDOMIDs) return;

    if (this._uncachedDOMIDs) { 
        for (var i = 0; i < this._uncachedDOMIDs.length; i++) {
            isc.ClassFactory.releaseDOMID(this._uncachedDOMIDs[i]);
        }
    }

    if (this._domIDs) {
        //this.logWarn("to release:"+ this.echo(this._domIDs));
        for (var i in this._domIDs) {
            isc.ClassFactory.releaseDOMID(this._domIDs[i]);
        }
    }
},

//>	@method	canvas.getCanvasName()	(A)
//			return the name for this object in the DOM
//
//		@return	(string)	name of this canvas in the DOM
//
//<
_$canvas:"canvas",
getCanvasName : function () {
    // NOTE: this is called by getHandle(), imgHTML() and a few other spots, it needs to be
    // fast.
    
    if (!this._canvasName) this._canvasName = this._getDOMID(this._$canvas, true);
    return this._canvasName;
},

_$canvasClipDiv:"canvas_clipDiv",
_getClipDivDOMID : function () {
    return this._getDOMID(this._$canvasClipDiv);
},


//>	@method	canvas.getTagStart()	(A)
//			return the start tag for this canvas
//		@group	drawing
//
//		@return	(string)	start tag for this canvas
//<
getTagStart : function (dontConcat) {
    

    //this.logWarn("at draw, coordinates are: " + this.getRect());
    var canvas = isc.Canvas,
        handleOverflow = this._getHandleOverflow();
        
    

    // if we're set to automatic zIndex, resolve to a number now
    if (this.zIndex == canvas.AUTO) this.zIndex = canvas.getNextZIndex();

    // get the ID of the eventProxy for this object so we can write it into the tag
    var eventProxy = (this.eventProxy ? this.eventProxy.ID : this.ID);

    var sizeArray = this._getInitialHandleSize(),
        width = sizeArray[0],
        height = sizeArray[1];
        
    // tabIndex, accessKey and focus.
    // ------------------
    // For accessability we need to support keyboard equivalents of everything you can
    // do with a mouse in ISC.
    // - tab and shift-tab are used to switch which ISC Canvas has the focus, such that
    //   it receives keyboard events.            
    // - Alt+<accessKey> will 'jump' focus to the ISC canvas with the specified accessKey
    //   [In Firefox 2.0 this has become Alt+Shift+[accesskey] ]
    //
    // Where possible we should do this by leveraging native tabIndex and accessKey
    // support. This will minimize our strange interaction cases with elements that
    // support native tabIndex behavior (such as form items), and allow us to get
    // native, familiar tabIndex type focus behavior for free.
    // It also may be required for support of screen reader software.
    // We also need to support updating the tabIndex/accessKey of the widget on the fly.

    // Native tabIndex / accessKey behaviour and considerations:
    // Not all browsers support focusability on every element type - some support focusability
    // only on form elements.  This creates a problem when we need to create keyboard
    // navigability for widgets whose rendering cannot possibly be based on the native <INPUT>
    // elements.
    //
    // ===== IE
    // Any element, including a DIV can be added to the tab-order of elements on a 
    // page by setting it's TABINDEX property.  This can also be updated on the fly.
    // When native focus is given to an element, the onfocus handler is called for this
    // element (and when focus is taken away, onblur is fired for the element).
    // No document.onfocus / onblur event is fired when focusing on an HTML element.
    //
    // Setting the tabIndex property for any element in IE to be negative will exclude it
    // from the page's tab order.
    // 
    // Setting the ACCESSKEY property on a div will cause focus to 'jump' to that div when
    // alt+accessKey is pressed.  Note that the accessKey can be set to any alpha-numeric
    // value (no symbols or full strings, case-insensitive for letters).
    //
    // Implementation for IE:
    //  -- Add TabIndex to the handle
    //  -- Add onfocus / onblur to keep track of which ISC element has focus
    //  -- Have widget.focus() and widget.blur() to call native element.focus() and
    //     element.blur()
    //
    // ===== Mozilla 
    // - Firefox 1.5 and above: identical to IE, except for the following additional
    //   workarounds
    //   Limitations:
    //      AccessKeys - in Moz FF 1.5, an accessKey set on a div is not respected.
    //      We workaround this by writing an empty "<a>" tag between the clip div and the 
    //      content div (after the content div), with a specified accessKey and a focus handler
    //      that puts focus into the widget.
    //      See _makeAccessKeyProxy().
    //      Focus on mouse down - In moz a div with a tabIndex will recieve focus when clicked.
    //      However we set the tabIndex on the clipDiv, not the content div (this is appropriate
    //      - if we set the tabIndex on the content div, the focus outline appears around the
    //      text rather than around the entire widget). In this case clicking on the content
    //      div fails to put focus onto the clip div. We workaround this by explicitly focusing
    //      in the widget on mousedown in EventHandler.js
    //
    // Previous to FF 1.5, there seems to be no support for TABINDEX on any elements other than
    // "interactive" HTML elements.  This includes form items, buttons and 
    // <a> </a> or <area> </area> tags *with an href set*. 
    // Note: <area> tags are used within the <map> tag of a client side image-map to
    // denote clickable areas.
    // Therefore we can't use the same approach as in IE or just writing a tabIndex
    // directly onto a widget's handle.
    // Possible approaches:
    //  - We could set an <a> tag around the handle with an onclick / onmouseDown handler
    //    that prevents the "href" from ever being activated.  
    //    Has some negative possible side effects, 
    //      - window.status gets updated when the <a> gets focus
    //      - SPACE may attempt to navigate the browser to the href specified
    //      - changes the style of text within the handle
    //  - We can draw an interactive tag offscreen for each focusable item, where:
    //      - onfocus / onblur handler for the offscreen tag would update the ISC focus.
    //      - interactive tag's tabIndex and accessKey matches the specified values for the
    //        widget
    //    This is the approach we've taken - we're drawing offscreen 'focusProxy' button 
    //    elements, to hold the widget's place in the page's native tab order.
    //
    // Note - to exclude focusable items from the page's tab order in Moz we must make
    // use of the property '-moz-user-focus' - setting this to "ignore" will exclude the
    // item from the page's tab order.  In order to make this more user-friendly, 
    // automatically interpret a negative specified tabIndex to mean the developer wants the
    // widget excluded from the tab order, and set this property to 'ignore' on the 
    // focusProxy.
    //
    // Implementation, for Moz pre ff 1.5:
    //  -- For each 'focusable' widget, create a button element called a focusProxy.
    //     This button is hidden - it is clipped by a parent div, and absolutely positioned
    //     behind the widget on the page (to ensure that when the element recieves native focus
    //     it is scrolled into view.
    //  -- Give the focusProxy the same tabIndex and accessKey as the widget.
    //  -- Write onfocus and onblur handlers for the focusProxy that put the 'virtual ISC
    //     focus' onto the widget
    //  -- Modify the 'setFocus()' method to natively focus on the focusProxy for a widget.
    //     (this in turn fires the onfocus handler that tells the EventHandler which widget
    //     has ISC-focus)
    //  -- Ensure the focusProxy stays 'in synch' with the widget's handle -- this means
    //      o update the visibility of the focusProxy as the visibility of the handle
    //        is changed - this correctly excludes hidden widgets from the page's tab order.
    //      o clear the focusProxy when the widget get's cleared (and write it back out if
    //        the widget is draw()n)
    //      o clear / redraw the focusProxy if the 'setCanFocus()' method updates a widget's
    //        focus-ability at runtime.
    //
    // =====
    // In Safari native buttons and native div's dont support keyboard access via tabbing.
    // FormItems do, so we use a TextArea as our focusProxy in this browser. Other than this
    // the logic is the same as for Moz pre FF 1.5
    //
    // Suppressing native focus outlines:
    // Native focus outlines show up in IE and Moz 1.5 and above (where we use native tab indices)
    // We want to suppress these for some widgets, such as menus.
    // This is controlled by the showFocusOutline property (defaults to true, set to false to suppress the
    // native focus outline).
    // In IE we use the proprietary 'hideFocus=true' attribute to suppress this
    // In Moz we use the css attribute '-moz-outline-style:none'

    if (!canvas._onFocus) {
        canvas._onFocus = " onfocus=";
        canvas._onBlur = " onblur=";
        canvas._tabIndex = " tabindex="
        canvas._accessKey = " accessKey=";
    }
    
    var isMoz = isc.Browser.isMoz;
    
    // Convert this.opacity to a usable value
    var opacity = this.opacity;
    // CSS opacity uses a decimal between 0 and 1 approach for specifying opacity (correct for
    // both Moz and Safari)
    if (!isc.Browser.isIE) {
        if (opacity != null) opacity = opacity / 100;
    }
    if (isc.Browser.isMoz) {
        
        if (this.smoothFade && (opacity == 1 || opacity == null)) opacity = 0.9999;
    }

    // for information on the necessity of this double DIV structure see "native size reporting
    // issues" comment
    if (this.useClipDiv) { 
        //>DoubleDiv
   
    	var containsIFrame = this.containsIFrame(),          
            cursor = this.getCurrentCursor();
        
        var focusString,
            nativeTabIndex = this._useNativeTabIndex;
        
        if (nativeTabIndex && this._canFocus()) {
            focusString = isc.SB.concat(
                canvas._onFocus, this._getNativeFocusHandlerString(),
                canvas._onBlur, this._getNativeBlurHandlerString(),
                !this.isDisabled() ? canvas._tabIndex + this.getTabIndex() : null,
                // Don't write an accessKey into the handle if we're going to use
                // an accessKeyProxy
                (!this._useAccessKeyProxy() && this.accessKey != null) ? 
                    canvas._accessKey + this.accessKey : 
                    null
            );
        }
        


        // use two DIVs: an inner one to hold the content, and an outer one for clipping        
        var output = isc.StringBuffer.concat(
        
            // the clipDiv            
            "<div id='" , this._getClipDivDOMID(),
            "' eventProxy=" , eventProxy,
            (this.className ? " class='" + this.className + "'" : ""),
            focusString,
            " style='",
                "POSITION:" , this.position,
                ";LEFT:" , this.left,
                "px;TOP:" , this.top,
                "px;WIDTH:" , width,
                "px;HEIGHT:" , height,
                "px;Z-INDEX:" , this.zIndex, 
                (this.visibility == canvas.INHERIT ? "" : ";VISIBILITY:" + this.visibility),
                (this.backgroundColor == null ? 
                    "" : ";BACKGROUND-COLOR:" + this.backgroundColor),
                (this.backgroundImage == null ? "" : 
                    ";BACKGROUND-IMAGE:url(" + this.getImgURL(this.backgroundImage) +")" +
                    ";BACKGROUND-REPEAT:"+this.backgroundRepeat +
                    (this.backgroundPosition ?
                        ";BACKGROUND-POSITION:"+this.backgroundPosition : "")),
                
                // border on outer DIV because it should not scroll
                (this.border ? ";BORDER:" + this.border : ""),

                // padding should scroll and should be included in the drawn content size,
                // so it goes on the contentDiv.
                // If this.padding is set, force the padding for the clipDiv to be zero.
                // Therefore if the style applied to the clipDiv has padding specified we
                // don't draw something with both sets of padding.
                
                (this.padding != null  || this._suppressOuterDivPadding ? ";PADDING:0px" : ""),
                
                // margin must also be on the outer container, since borders are
                this._getMarginHTML(),
                // In Moz we set style.-moz-opacity to a value between zero and one to get opacity
                // In Safari we set style.opacity to a value between zero and one.
                (opacity != null ? 
                    (this._useMozOpacity ? ";-moz-opacity:" : ";opacity:") + opacity :
                    ""),
                // use box sizing model where specified size includes border and padding
                (isMoz ? ";-moz-box-sizing:border-box" : null),
                // In Moz, if we are using native tab-indices, the dotted focus 
                // outline by default appears 1px outside the clip-div.
                // This would be fine except it is clipped by any parents of this widget and
                // obscured by any other siblings that are adjacent to the widget and have a
                // higher z-index.
                // Moz has a useful css extension -moz-outline-offset which allows us to 
                // have the focus outline render a specified distance from the element, including
                // inside, via applying a negative value. This avoids the problem with parents
                // and siblings obscuring the outline.
                // In most cases we use this and it gives us a reasonable solution for a focus
                // outline.
                // However the contents of the content-div  div will obscure the
                // outline if it contains some solid element that extends to the edge of the
                // canvas, such as a  grid-renderer's table, or an image.
                // We could possibly resolve this issue by using the "focus" pseudo css class
                // to apply a custom border in this case instead of relying on the native focus
                // outline.  However this could shift internal content, and besides, we expect
                // this bug will be fixed.
                // For now just provide a property so we can set the offset depending on the
                // widget in question to give as much flexibility as possible.   
                // Note: If the widget has a border, or padding, the focus outline shows up 
                //  over the border / padding rather than over the content, when the specified 
                //  offset is -1px.
                // Note: there is a further Moz bug with widgets showing native scrollbars where,
                // when scrolling, a series of horizontal lines appears over the content due to
                // incorrect redraw of the focus outline.
                (isMoz && nativeTabIndex && this.mozOutlineOffset != null 
                    ? ";-moz-outline-offset:" + this.mozOutlineOffset : null),
                // We also allow customization of the color of the Moz Focus outline.
                // By default, in FF 1.5 (tested against 1.5.0.3) the color is supposed to invert
                // whatever it's sitting on top of - however when the background is gray                    
                // the focus outline is the same color so isn't visible.
                // We therefore allow per-widget customization of the color.
                (isMoz && nativeTabIndex && this.mozOutlineColor != null 
                    ? ";-moz-outline-color:" + this.mozOutlineColor : null),
                
                (isMoz && nativeTabIndex && !this.showFocusOutline
                    ? ";-moz-outline-style:none" : null),
                    
                ";OVERFLOW:",
                handleOverflow,
                
                ";' ONSCROLL='return " + eventProxy + "._handleCSSScroll()'>",
                    
            // the contentDiv
            "<div id='" , this.getCanvasName(),
            "' eventProxy='" , eventProxy,
            (this.textDirection != null ? "' dir='" + this.textDirection : ""),            
            "' style='POSITION:relative;VISIBILITY:inherit",

                // nested DIV size detection and float:left - see comments "native size
                // reporting issues" at bottom of Canvas.js for details
                (!this._useMozScrollSize && !isc.Browser.isOpera && 
                    !((isc.Browser.isSafari || isc.Browser.isFirefox) && containsIFrame) ? 
                    ";FLOAT:left" : ""),
                ";Z-INDEX:" , this.zIndex,
                (cursor == canvas.AUTO ? "" : ";CURSOR:" + cursor),
                // padding should be included in the drawn content size, so it goes on the
                // contentDiv
                (this.padding != null ? ";PADDING:" + this.padding + "px" : ""),
                // Unexposed per-side padding
                (this.topPadding != null ? ";padding-top:" + this.topPadding + "px" : ""),
                (this.bottomPadding != null ? ";padding-bottom:" + this.bottomPadding + "px" : ""),
                (this.leftPadding != null ? ";padding-left:" + this.leftPadding + "px" : ""),
                (this.rightPadding != null ? ";padding-right:" + this.rightPadding + "px" : ""),
                ";'>"
        );
        
        //<DoubleDiv
    } else { // Browsers where we can use a single DIV, currently IE4+    
        //>SingleDiv
        if (!canvas._divHTML) {
            canvas._absolutePos = " style='POSITION:absolute;LEFT:";
            canvas._relativePos = " style='POSITION:relative;LEFT:";
            canvas._className = " class='";
            canvas._closeClassName = "'";
            canvas._visibility = ";VISIBILITY:";
            canvas._$cursor = ";CURSOR:";
            
            var divHTML = canvas._divHTML = [];
            divHTML[0] = "<div id=";
            // [1] ID
            divHTML[2] = " eventProxy=";
            // [3] eventProxy
            // [4] optional " CLASS=";
            // [5] optional className
            // [6] optional close className
            // [7] optional textDirection
            // [8] " STYLE='POSITION:" relative or absolute + ";LEFT:"
            // [9-14] left
            divHTML[15] = "px;TOP:";
            // [16-21] top
            divHTML[22] = "px;WIDTH:";
            // [23-27] width
            divHTML[28] = "px;HEIGHT:";
            // [29-33] height

            divHTML[34] = "px;Z-INDEX:";
            // [35-41] zIndex

            divHTML[44] = ";OVERFLOW:";
            // [45] overflow
            // [46] visibility
            // [47] visibility
            // [48] background-color
            // [49] background-image
            // [50] Moz box sizing
            // [51] cursor
            // [52] cursor
            // [53] margin
            // [54] padding
            // [55] border
            // [56] opacity
            // [57] flash filter
            // [58] unused
            // NOTE: in IE, DIV scroll events can't be captured at the window level.  
            divHTML[59] = "' ONSCROLL='return ";
            // [60] eventProxy
            divHTML[61] = "._handleCSSScroll()' ";
            // [gap]
            // [several slots 63+] focus/blur/tabIndex/accessKey
        }
        var divHTML = canvas._divHTML;
        
        divHTML[1] = this.getCanvasName();
        divHTML[3] = eventProxy;
        // optional className (note that Button and other table-based Canvii omit className at
        // the DIV level and apply it to the cell)
        if (this.className != null) {
            divHTML[4] = canvas._className;
            divHTML[5] = this.className;
            divHTML[6] = canvas._closeClassName;
        } else {
            divHTML[4] = divHTML[5] = divHTML[6] = null;
        }
        
        divHTML[7] = (this.textDirection != null ? " dir=" + this.textDirection : null);
        
        divHTML[8] = (this.position == canvas.RELATIVE ? canvas._relativePos :
                      canvas._absolutePos);

        // 6 slots mostly due to the tendency to use "-10000" as a large offscreen coordinate
        isc._fillNumber(divHTML, this.left, 9, 6);
        isc._fillNumber(divHTML, this.top, 16, 6);
        isc._fillNumber(divHTML, width, 23, 5);
        isc._fillNumber(divHTML, height, 29, 5);

        
        if (this.zIndex != canvas.AUTO) isc._fillNumber(divHTML, this.zIndex, 35, 9);
        else {
            divHTML[35] = this.zIndex;
            divHTML[36] = divHTML[37] = divHTML[38] = divHTML[39] = 
                    divHTML[40] = divHTML[41] = divHTML[42] = divHTML[43] = null;
        }

        divHTML[45] = handleOverflow;
        if (this.visibility != canvas.INHERIT) {
            divHTML[46] = canvas._visibility;
            divHTML[47] = this.visibility;
        } else {
            divHTML[46] = divHTML[47] = null;
        }
        divHTML[48] = (this.backgroundColor == null ? null : 
                       ";BACKGROUND-COLOR:" + this.backgroundColor);
        divHTML[49] = (this.backgroundImage == null ? null : 
                       ";BACKGROUND-IMAGE:url(" + this.getImgURL(this.backgroundImage) + 
                       ");BACKGROUND-REPEAT:"+this.backgroundRepeat +
                    (this.backgroundPosition ?
                        ";BACKGROUND-POSITION:"+this.backgroundPosition : ""));
        
        divHTML[50] = (isMoz ? ";-moz-box-sizing:border-box" : null);
        
        var cursor = this.getCurrentCursor();
        if (cursor == canvas.AUTO) {
            divHTML[51] = divHTML[52] = null;
        } else {
            divHTML[51] = canvas._$cursor;
            divHTML[52] = cursor;
        }
        divHTML[53] = this._getMarginHTML();
        divHTML[54] = (this.padding != null ? ";PADDING:" + this.padding + isc.px : null);
       // Unexposed per-side padding
       if (this.topPadding != null) 
           divHTML[54] = (divHTML[54] || "") + ";padding-top:" + this.topPadding + "px";
       if (this.bottomPadding != null) 
           divHTML[54] = (divHTML[54] || "") + ";padding-bottom:" + this.bottomPadding + "px";
       if (this.leftPadding != null) 
           divHTML[54] = (divHTML[54] || "") + ";padding-left:" + this.leftPadding + "px";
       if (this.rightPadding != null) 
           divHTML[54] = (divHTML[54] || "") + ";padding-right:" + this.rightPadding + "px";
  
       divHTML[55] = (this.border ? ";BORDER:" + this.border : null);
        if (isc.Browser.isIE) {
            divHTML[56] = (opacity == null ? null :
                   ";filter:progid:DXImageTransform.Microsoft.Alpha(opacity="+opacity+")");

            
            if (this._avoidRedrawFlash) {
                divHTML[57] = ";filter:progid:DXImageTransform.Microsoft.iris(irisStyle=circle)"
            } else {
                divHTML[57] = null;
            }
        } else {
            if (opacity != null) {
                divHTML[56] = (this._useMozOpacity ? ";-moz-opacity:" : ";opacity:") + opacity;
            } else {
                divHTML[56] = null;
            }
        }
        divHTML[60] = eventProxy;

        var lastSlot = 64;
        if (this._canFocus() && this._useNativeTabIndex) {
            divHTML[64] = canvas._onFocus;
            divHTML[65] = this._getNativeFocusHandlerString();
            divHTML[66] = canvas._onBlur;
            divHTML[67] = this._getNativeBlurHandlerString();
            if (!this.isDisabled()) {
                divHTML[68] = canvas._tabIndex;
                isc._fillNumber(divHTML, this.getTabIndex(), 69, 5);
                //divHTML[67] = this.getTabIndex();
                if (this.accessKey != null) {
                    divHTML[74] = canvas._accessKey;
                    divHTML[75] = this.accessKey;        
                    lastSlot = 76;
                } else lastSlot = 74;

                if (!this.showFocusOutline) {
                    if (!canvas._$hideFocus) canvas._$hideFocus = " hideFocus=true";
                    divHTML[lastSlot] = canvas._$hideFocus;
                    lastSlot += 1;
                }

            } else lastSlot = 68;
        }

        

        // trim focus-related strings left in the template by the last widget, and end
        // start tag
        divHTML.length = lastSlot;
        divHTML[lastSlot] = this._$rightAngle;

        // code to grab a sample of the HTML written for the first instance of each class
        /*
        var className = this.getClass();
        if (!canvas._sampled) canvas._sampled = {};
        if (!canvas._sampled[className]) {
            this.logWarn("html for first instance of:" + className + ": " + divHTML.join(""));
            canvas._sampled[className] = true;
        }
        */

        if (dontConcat) return divHTML;
        return divHTML.join(isc.emptyString);
        //<SingleDiv
    }
     
    return output;
},



_$marginLeft : "MARGIN-LEFT:",
_$marginRight : "MARGIN-RIGHT:",
_$marginTop : "MARGIN-TOP:",
_$marginBottom : "MARGIN-BOTTOM:",
_$margin : "MARGIN:",

_getMarginHTML : function () {
    
    // optimization: if we have nothing that would introduce automatic per-side margin
    // settings..
    if (!this._edgesAsPeer() && this._attachedPeerMap == null) {
        // don't write out margins CSS if we have no margins setting
        if (this.margin == null) return null;
        // write out only a symmetric margin setting
        return isc.SB.concat(isc.semi, this._$margin, this.margin, isc.px);
    }

    // Support assymetric margins if necessary.
    var margins = this._calculateMargins(),
        cssText = isc.SB.concat(
             isc.semi, this._$marginLeft, margins.left, isc.px,
             isc.semi, this._$marginRight, margins.right, isc.px,
             isc.semi, this._$marginTop, margins.top, isc.px,
             isc.semi, this._$marginBottom, margins.bottom, isc.px
        );
    //this.logWarn("margins: " + cssText);
    return cssText;
},


//>	@method	canvas.getTagEnd()	(A)
//			return the end tag for this canvas
//		@group	drawing
//
//		@return	(string)	end tag for this canvas
//<
_singleDIV : "</div>",
_doubleDIV : "</div></div>",
getTagEnd : function () {
    
    //>DoubleDiv two closing tags if clipDiv is being used.
    if (this.useClipDiv) return this._doubleDIV;
    //<DoubleDiv

    //>SingleDiv
    return this._singleDIV;
    //<SingleDiv
},

// _getHandleOverflow() 
// Internal method to determine the desired overflow setting for the widgets handle.
// Note - this is used for the clipHandle (not the contentHandle) if we're showing a clipHandle.
// Called from both getTagStart() and setOverflow()
_getHandleOverflow : function () {
    var overflow = this.overflow;
    var scrolling = (this.overflow == isc.Canvas.SCROLL || this.overflow == isc.Canvas.AUTO),
        customScrolling = scrolling && this.showCustomScrollbars,
        nativeScrolling = scrolling && !this.showCustomScrollbars;
    // when we use custom scrollbars, we tell the browser that the overflow should be hidden so
    // that the browser doesn't draw scrollbars.  Then we have our scrollbars move the hidden
    // overflow into view via scripting.
    // See getScrollingMechanism(), and comments near showCustomScrollbars definition above.
    if (this.overflow == isc.Canvas.HIDDEN || customScrolling)
    {
        
        if (this._useMozScrollbarsNone) {
            overflow = this._canScrollHidden ? this._$hidden : "-moz-scrollbars-none";
            this._useMozScrollSize = true;
        } else {
            overflow = this._$hidden;
        }

    } else if (isc.Browser.isOpera && this.overflow == isc.Canvas.VISIBLE) {
        overflow = this._$hidden;
    } else if (isc.Browser.isMoz) {
        if (nativeScrolling) this._useMozScrollSize = true;
        
        else if (this._useMozScrollbarsNone) {
            overflow = this._canScrollHidden ? this._$hidden : "-moz-scrollbars-none";
            this._useMozScrollSize = true;
        }
    }

    // if a clipDiv is being used, it will need to be set to overflow:hidden if the canvas is to
    // behave as overflow:clip-h/clip-v, because an overflow:visible DIV won't clip contained
    // content, and we can't use clip regions in NS6.
    if (this.useClipDiv && 
        (this.overflow == isc.Canvas.CLIP_H || this.overflow == isc.Canvas.CLIP_V)) 
    {
        overflow = this._$hidden;
    }
    return overflow;
},

// _getInitialHandleSize()
// Internal function to return the specified width / height to write out to the handle initially
// used by getTagStart() and setOverflow()
// NOTE: returns the same Array instance every time.  Retrieve values before calling again.
_fillArray : [],
_getInitialHandleSize : function () {
    
    var width = this.getInitialWidth(),
        height = this.getInitialHeight();
     
    return this._adjustHandleSize(width, height);
},

// NOTE: these two very advanced functions allow you to specify an initial size for the handle
// which differs from this.width/height.  To be really complete, allowing the handle to have a
// completely different size from the specified size of the Canvas, similar entry points should
// exist for setHandleRect().
getInitialWidth : function () {
    return this.getWidth();
},

getInitialHeight : function () {
    return this.getHeight();
},

// In Mozilla we explicitly specify border-box sizing for canvii.
// In other browsers we have no control over border-box vs content-box sizing, so we
// rely on the default browser behavior for DIVs
isBorderBox:(isc.Browser.isMoz || isc.Browser.isBorderBox),


//> @method canvas._adjustHandleSize()  (I)
//      Internal helper method.
//      Given a desired width and height, return the width and height we must actually write into
//      the handle such that when drawn, the widget, will be the specified size, including all
//      border and padding (effectively fitting the 'content box' sizing model.)
//      Also adjusts for space taken up by custom scrollbars if required.
//      Called from getTagStart() and setHandleRect();
//
//      NOTE: returns the same Array instance every time.  Don't call again without retrieving
//      values first.
//
//      @visibility internal
//      @group  sizing
//
//      @return     (array)     2 element array containing [width,height] to write into the handle
//<
_adjustHandleSize : function (width, height) {

    
    var margins = this._calculateMargins();

    // If passed null for width / height just return it
    if (width != null) {
        // when using custom scrollbars, we shrink the handle to leave room for the scrollbar
        if (this.showCustomScrollbars && this.vscrollOn) {
            width -= this.getScrollbarSize();
            
        }

        // The CSS2 box model specifies that content can be surrounded, from inside to out, by
        // padding, then a border, then margins.
        //     http://www.w3.org/TR/REC-CSS2/box.html
        //
        // The CSS2 spec also says that the width and height specified for an element should be
        // taken as the width and height *without* padding, border or margins (the "content-box"
        // model).  Older browsers implemented width/height as meaning width/height *with*
        // border and padding (the "border-box" model).
        //
        // In CSS3, you will be able to specify what sizing model you want via the "box-sizing:"
        // attribute.  Some browsers already support this.
        //
        // The border-box model is much saner, as the alternative is that in order to get
        // something to be a particular size in order to fit into a layout, you have to subtract
        // off the size of the border and padding up front, which implies you must know the
        // border and padding size despite the fact that such things are supposed to be
        // externalized into style sheets.
        //
        // We take this a step further - the specified size for any widget will be the drawn size 
        // including border, padding, AND margins.
        // This is desirable because it allows the developer to handle separating widgets within
        // layouts by applying margins to the widgets.
        // We handle this by subtracting the margins (and in browsers that cannot be told to use 
        // the border-box model, padding and border) from a widget, and applying that adjusted
        // size to the handle.
        // 
        //
        
        // In order to get the desired handle size (to write into the DOM):
        // In all browsers subtract margin sizes.
        // 
        width -= (margins.left + margins.right);
        //width -= (this.getLeftMargin() + this.getRightMargin());
        
        if (this.isBorderBox) {
            // border-box (either DIV structure): nothing
        } else if (this.useClipDiv) {
            // double DIV content-box, eg, Safari, Opera
            // if padding is explicitly specified, it gets placed on the content div, so deduct
            // border only.  If padding is from the style, it's applied to the clip div, so
            // deduct it as well.
            if (this.padding != null) {
                width -= this.getHBorderSize();
            } else {
                width -= this.getHBorderPad();
            }
        } else {
            // single DIV content-box, eg, IE strict
            width -= this.getHBorderPad();
        }
    }
    
    if (height != null) {
    
        if (this.showCustomScrollbars && this.hscrollOn) {
            height -= this.getScrollbarSize();  
        }        

        height -= (margins.top + margins.bottom);

        if (this.isBorderBox) {
        } else if (this.useClipDiv) {
            if (this.padding != null) {
                height -= this.getVBorderSize();
            } else {
                height -= this.getVBorderPad();
            }
        } else {
            height -= this.getVBorderPad();
        }
    }
    
    // If the sizes are negative default them to 1
    
    if (width != null && width < 1) {
        this.logInfo("Specified width:" + this.getInitialWidth() + " adjusted for border, margin, " +
                     "and scrollbars would cause initial handle size to be less than or equal to " +
                     "zero, which is not supported. Clamping handle width to 1px.", "sizing"); 
        width =1;
    }
    if (height != null && height < 1) {
        this.logInfo("Specified height:" + this.getInitialHeight() + " adjusted for border, margin, " +
                     "and scrollbars would cause initial handle size to be less than or equal to " +
                     "zero, which is not supported. Clamping handle height to 1px.", "sizing");    
        height =1;
    }
    
    // NOTE: reuse an Array
    var arr = this._fillArray;
    arr[0] = width;
    arr[1] = height;
    return arr;
},

// _getNativeFocusHandlerString() and _getNativeBlurHandlerString()
// These methods return the native onblur / onfocus handler function strings for use when 
// using native focus / blur behavior.  Used by getTagStart / getFocusProxyHTML when writing
// out the onblur / onfocus handler attributes.
_$focusStart : "isc.EH.focusInCanvas(", 

_$mozFocusStart : "if(event.target!=this)return;isc.EH.focusInCanvas(",
  
_$blurStart : "if(window.isc)isc.EH.blurFocusCanvas(", 
_$focusEnd : ",true);",
_getNativeFocusHandlerString : function (unquoted) {
    var ID = this.getID();
    var quote = unquoted ? null : this._$singleQuote;
    if (isc.Browser.isMoz) 
            return isc.SB.concat(quote, this._$mozFocusStart, ID, this._$focusEnd, quote);
    return isc.SB.concat(quote, this._$focusStart, this.getID(), this._$focusEnd, quote);
},

_getNativeBlurHandlerString : function (unquoted) {
    var quote = unquoted ? null : this._$singleQuote;
    return isc.SB.concat(quote, this._$blurStart, this.getID(), this._$focusEnd, quote);
},

// _getNativeFocusHandlerMethod / _getNativeBlurHandlerMethod
// Returns the native focus / blur handlers as a method constructed from the native focus/blur
// handler strings.
// This can then be assigned directly to the handle's onfocus / onblur attribute after
// the handle has been written out.
_getNativeFocusHandlerMethod : function () {
    if (!this._nativeFocusHandlerMethod) {
        this._nativeFocusHandlerMethod = new Function ("event", this._getNativeFocusHandlerString(true));
    }
    return this._nativeFocusHandlerMethod;
},
_getNativeBlurHandlerMethod : function () {
    if (!this._nativeBlurHandlerMethod) {
        this._nativeBlurHandlerMethod = new Function ("event", this._getNativeBlurHandlerString(true));
    }
    return this._nativeBlurHandlerMethod;
},
// Handles: pointers to the Canvas' DOM representation
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getHandle()	(A)
//			get the handle to this layer
//		@group	handles
//
//		@return	(handle)	handle to this layer
//<
getHandle : function () {
    if (isc._traceMarkers) arguments.__this = this;

    
    if (this.destroyed) {
        this.logWarn("Attempt to access destroyed widget in the DOM - " +
            "destroy() called at invalid time (eg: mid-draw) or invalid method " +
            "called on destroy()d widget. Stack Trace:" + this.getStackTrace());
        
    }

    // don't look for the handle unless we're drawn
    
    if (!(this._handleDrawn || this._drawn)) return null;

    // if the handle is not already defined, find it
    if (this._handle == null) {
        // get the ID we wrote into the DOM for the handle
        var elementId = this.getCanvasName();
        // and get the handle by id
        this._handle = this.ns.Element.get(elementId);

        // if we can't find the handle, since we're supposedly drawn, this is an error
        if (this._handle == null) {
            this.logWarn("Unable to find handle for drawn Canvas, elementId: " + elementId);
        }
    }
    return this._handle;
},

//> @method canvas.getClipHandle() (A)      
//
//      @group  handles
//      @return (handle)  clipDiv handle to this layer
//<
getClipHandle : function () {
    // if we're not using a separate clip vs content DIV, the clip handle is just the handle
    if (!this.useClipDiv) return this.getHandle();

    //>DoubleDiv
    // don't look for the handle unless we're drawn
    if (!(this._handleDrawn || this._drawn)) return null;

    // if the handle is not already defined, find it
    if (this._clipDiv == null) {
        // get the document the handle would be in 
        var elementId = this._getClipDivDOMID();
        
        // and get the handle by id
        this._clipDiv = this.ns.Element.get(elementId);

        // if we can't find the handle, since we're supposedly drawn, this is an error
        if (this._clipDiv == null) {
            this.logWarn("Unable to find clipHandle for drawn Canvas, elementId: " + elementId);
        }
    }
    return this._clipDiv;
    //<DoubleDiv
},

//> @method canvas.getScrollHandle() (A)      
//      If we're scrolling by setting the native scroll position on some DOM element this
//      method gives us a pointer to that element.
//
//      @group  handles
//      @return (handle)  scroll handle of the DOM element
//<

getScrollHandle : function () {
    // default implementation uses this widget's clipHandle.
    return this.getClipHandle();
},

// _getURLHandle()
// Get the handle of the IFrame we used to load content (when using contentsURL and
// contentsType:"page")
_getURLHandle : function () {
    if (!this.containsIFrame()) return null;
    var handle = this.getHandle();
    
    if (!handle) return null;
    // The IFRAME should be rendered as the only child of the handle. Do a sanity check to
    // verify we are looking at an IFRAME element.
    handle = handle.firstChild;
    if (handle && handle.tagName && (handle.tagName.toLowerCase() == "iframe")) return handle
    
    return null;
},

//>FocusProxy
//> @method canvas._getFocusProxyHandle() (I)
//
//      @group  handles
//      @return (handle) - handle for the 'focusProxy' button.  <code>null</code> if 
//                         this._useFocusProxy is false.
//<
_getFocusProxyHandle : function () {
    if (!this._useFocusProxy || !this._hasFocusProxy) return null;
    
    if (!this._focusProxy) {
        var elementId = this.getCanvasName() + "__focusProxy";
        this._focusProxy = this.getDocument().getElementById(elementId);
    }
    
    return this._focusProxy;
},

//> @method canvas._getFocusProxyParentHandle() (I)
//
//      @group  handles
//      @return (handle) - handle for the parent div for the 'focusProxy' button.
//                         <code>null</code> if this._useFocusProxy is false.
//<
_getFocusProxyParentHandle : function () {
    if (!this._useFocusProxy) return null;
    if (!this._focusProxy) this._focusProxy = this._getFocusProxyHandle();
    
    return (this._focusProxy != null ? this._focusProxy.parentNode : null);
},
//<FocusProxy


//>	@method	canvas.getStyleHandle()	(A)
//		Return the style handle for this canvas.  
//		 This is what we use to set some of the physical properties
//		 of canvases, such as the visibility, left, etc.
//		 
//		@group	handles
//		@return	(handle)	style handle to this layer
//<
getStyleHandle : function () {
    if (!this._styleHandle) {
        
        this._styleHandle = (this.getClipHandle() ? this.getClipHandle().style : null);
    }
    return this._styleHandle;
},

// --------------------------------------------------------------------------------------------

//>	@method	canvas.setUpEvents()	(A)
//			set up the handle for this canvas to respond properly to mouse/keyboard events
//		@group	events
//<
// On all browsers, all Canvii write an "eventProxy" attribute into their DOM representation
// (DIV element), which the isc.EventHandler uses to route events to the Canvas which drew the
// DOM elements.
setUpEvents : function () {
    // register to receive drop events, if necessary
    if (this.canAcceptDrop) this.ns.EH.registerDroppableItem(this);
},

// Creating children
// --------------------------------------------------------------------------------------------

// make sure that everything in the children array is a canvas, and has us as its
// parentElement.  if either is not true, call addChild() to add it as a proper child
_instantiateChildren : function (children) {

	// start with a fresh children array, 
	//		in case any children add peers (which should appear directly after that child).
    // NOTE: creating a fresh Array here is also key when Canvas.children is set as an
    // inherited property, because each Canvas needs to have a unique children Array
    if (!children) children = this.children;
    if (!children) return;
	this.children = [];

	for (var i = 0, child; i < children.length; i++) {
		child = children[i];
		
        if (!child) continue;

		// if the child is not a canvas, or doesn't recognize us as its parent
		// call addChild() to create it and add it to our list of children
		if (!isc.isA.Canvas(child) || child.parentElement != this) {
			this.addChild(child);

		// otherwise, it's already been set up correctly (by a previous call to addChild())
		//	so we'll just add it to our children array
		} else {
			this.children.add(child);
		}
	}
},


_$autoChildPrefix:"autoChild:",
_lazyAutoChildCreate : function (name) {
    name = name.substring(this._$autoChildPrefix.length);

    //this.logWarn("lazy creation of autoChild: " + name);

    // try to figure out whether this autoChild should be created by the current widget, or, if
    // this widget is itself an autoChild, by it's creator
    var defaultsName = this._getDefaultsName(name);
    var creator = this[defaultsName] ? this :   
                    isc.isA.Canvas(this.creator) && this.creator[defaultsName] ? this.creator :
                    this;

    if (isc.isA.Canvas(creator[name])) return creator[name];

    // NOTE: when creating autoChildren in this method, we want:
    // - unconditional creation, unlike addAutoChild()
    // - no adding to parent (callers generally have a parent in mind), unlike addAutoChild()
    // - do want to set up this[name], like addAutoChild()
    return (creator[name] = creator.createAutoChild(name));
},

// create or find a Canvas based on the passed string or properties, or return it if it's
// already a Canvas.  Used to allow canvas.children, layout.members, window.items, etc to
// accept various standard ways of specifying Canvii
createCanvas : function (canvas) {
    if (isc.isA.Canvas(canvas)) return canvas;

    if (canvas == null) return;

    if (isc.isA.String(canvas)) {
        // the "autoChild:[childName]" format allows lazy instantiation of autoChildren from eg
        // section.items or tab.pane
        if (isc.startsWith(canvas, this._$autoChildPrefix)) { 
            return this._lazyAutoChildCreate(canvas);
        }
        
        // otherwise assume the id of a global widget
        return window[canvas];
    }

    var autoChildName = canvas.autoChildName;
    if (autoChildName) {
        // NOTE: we want just creation here, not adding to parent, as addAutoChild would do
        return this[autoChildName] = this.createAutoChild(autoChildName, canvas);
    }

    // new child provided as a properties block - create it
    var cons = canvas._constructor;
    // if constructor isn't provided or doesn't name a class, default to Canvas
    if (cons == null || isc.ClassFactory.getClass(cons) == null) {
        cons = isc.Canvas;
    }
    canvas._constructor = null;
    
    // prevent autoDraw
    canvas.autoDraw = false; 

    return isc.ClassFactory.newInstance(cons, canvas);
},

createCanvii : function (canvii) {
    if (canvii == null) return;
    for (var i = 0; i < canvii.length; i++) {
        canvii[i] = this.createCanvas(canvii[i]);
    }
    return canvii;
},

// setEventProxy() - update the eventProxy for this widget at runtime
setEventProxy : function (newProxy) {
    // clear any back-references from current eventProxy
    var oldProxy = this.eventProxy;
    if (oldProxy == newProxy) return;
    
    if (oldProxy != null) {
        oldProxy._proxiers.remove(this);
        // Clear the eventProxy pointer from the DOM object
        
        if (this.isDrawn()) {
            if (this.getHandle() != null) this.getHandle().eventProxy = null;
            if (this.getClipHandle() != this.getHandle()) this.getClipHandle().eventProxy = null;
        }
    }
    
    // set this.eventProxy to the newProxy passed in (may be null, in which case we clear out
    // the eventProxy).
    this.eventProxy = newProxy;
    
    if (newProxy != null) {
        if (!isc.isA.Canvas(newProxy)) {
            this.logWarn("setEventProxy() passed invalid eventProxy - clearing this property");
            this.eventProxy = null;
        } else {
            if (newProxy._proxiers == null) newProxy._proxiers = [];
            newProxy._proxiers.add(this);
        }
    }
    // Have to redraw, so eventHandling doesn't get confused about what the eventproxy is
    // Make this an immediate redraw, so *Any* subsequent events go through to the appropriate
    // proxy.
    if (this.isDrawn()) this.redraw("eventProxy updated");
},

// clearEventProxy() - clear this widget's eventProxy at runtime
clearEventProxy : function () {
    this.setEventProxy();
},
    
// Adding and Removing Children and Peers
// --------------------------------------------------------------------------------------------

//>	@method	canvas.addChild()   ([])
// Adds newChild as a child of this widget, set up a named object reference (i.e., this[name])
// to the new widget if name argument is provided, and draw the child if this widget has been
// drawn already.
// <P>
// If newChild has a parent it will be removed from it. If it has a master, it will be detached
// from it if the master is a child of a different parent. If newChild has peers, they'll be
// added to this widget as children as well.
//
//  @visibility external
//	@group	containment
//	@param	newChild		(canvas)	new child canvas to add
//	@param	[name]			(string)	name to assign to child (eg: this[name] == child)
//  @param  [autoDraw]      (boolean)   if false, child will not automatically be drawn (only
//                                          for advanced use)
//	@return	(canvas)	the new child, or null if it couldn't be added
//<
addChild : function (newChild, name, autoDraw) {
    
    if (isc._traceMarkers) arguments.__this = this;
	if (!newChild) return null;	// just to be safe

    if (newChild == this) {
        this.logWarn("Attempt to add a child to itself");
        return;
    }

	//this.logInfo("addChild() called on " + newChild + " : parent is drawn() " + this.isDrawn() 
	//			+ " : child is " + (isA.Canvas(newChild) ? (newChild.isDrawn() ? "drawn " : "undrawn ") + newChild.Class
	//													 : "object literal " + Echo.asString(newChild)), "drawing");

    // instantiate the child on the fly if it hasn't been created yet (autodraw is suppressed)
	if (!isc.isAn.Instance(newChild)) newChild = this.createCanvas(newChild);

	if (!isc.isA.Canvas(newChild)) {
		//>DEBUG
		this.logWarn("addChild(): trying to install a non-canvas as a child.  Returning.");
		//<DEBUG	
		return null;
	}

	// if newChild already recognizes this canvas as its parent, bail
	if (newChild.parentElement == this) return newChild;
    
    var wasDrawn = newChild.isDrawn();
	
	// remove the child from its old parent, if any
	if (newChild.parentElement) newChild.deparent(name);

    // Remove the child from the top level canvas list - it's no longer a top level canvas
    isc.Canvas._removeFromTopLevelCanvasList(newChild);

    // drop the drawContext to ensure the child does not try to draw in some arbitrary DOM
    // location instead of inside it's new parent
    if (newChild.drawContext) newChild.drawContext = null;
    if (newChild.htmlElement) newChild.htmlElement = null;

	// attach the child to its new parentElement (this canvas) and topElement
	newChild.parentElement = this;
	newChild.topElement = (this.topElement || this);
	// update topElement for the child's children, if any
    // (This method will recursively be called on each child / descendant)
	newChild._updateChildrenTopElement();

	if (name) this[name] = newChild;
	if (!this.children) this.children = [];
	// the following conditional allows for direct initialization of the children[] property
	if (!this.children.contains(newChild)) this.children.add(newChild);
	

	// detach the child from its master, if the master's parent is different
	var childsMaster = newChild.masterElement;
	if (childsMaster && childsMaster.parentElement != this) {
		childsMaster.peers.remove(newChild);
		if (childsMaster[name] == newChild) childsMaster[name] = null;
		newChild.masterElement = null;
	}

	// add the child's peers, if any, as children of this parent
	// it's important that this is done *after* newChild is attached to its parent, so the
    // peers don't think they're being moved to a different parent and break their peer/master
    // link
	if (newChild.peers) {
        for (var i = 0; i < newChild.peers.length; i++) this.addChild(newChild.peers[i]);
    }

	// if the child had already been drawn (therefore outside of this canvas), clear it
	if (newChild.isDrawn()) newChild.clear();
    
    // If the page isn't done loading, and we had to clear in order to reparent, warn the
    // developer - chances are they failed to set autoDraw:false
    if (wasDrawn && !this.warnAboutClear && !isc.Page.isLoaded()) {
        this.logWarn("Adding already drawn widget:" + newChild.getID() + " to new parent:" + 
                    this.getID() + ". Child has been cleared so it can be drawn inside the new " +
                    "parent. This may be a result of autoDraw being enabled for the child.");   
    }

    // Calculate sizes that are expressed as a percentage of parent size, if necessary; this is
    // done after clear so that unnecessary redraws are avoided.  If the parent has not yet
    // drawn, no need, as this happens as part the call to layoutChildren() during draw.
    if (this.isDrawn()) newChild._resolvePercentageSize();

    // Ensure that if any clickmasks are showing our child is at the same level as we are
    // wrt them.
    
    var EH = this.ns.EH;
    if (EH.clickMaskUp()) {

        var CMIDs = EH.getAllClickMaskIDs();
        for (var i = CMIDs.length -1; i >= 0; i--) {
            var parentMasked = EH.targetIsMasked(this, CMIDs[i]);
            if (!parentMasked) {
                EH.addUnmaskedTarget(newChild, CMIDs[i]);
                // We're iterating down from the top - once a widget is over one mask it's also
                // over any masks below that one. Therefore we don't need to keep iterating 
                // down to the bottom adding unmasked targets.
                break;
            } else {
                // If we're masked by our child is not, mask the child.
                var childMasked = EH.targetIsMasked(newChild, CMIDs[i]);
                if (!childMasked) EH.maskTarget(newChild, CMIDs[i]);
            }
        }
    }
    
    // for very advanced callers, support not drawing the child automatically
    if (autoDraw == false || newChild._dontDrawOnAddChild) {
        // support one-time flag.  Kind of a hack, but there are many codepaths that ultimately
        // call addChild()
        newChild._dontDrawOnAddChild = null;
        return newChild;
    }
    
    
    var tabIndexManaged = false,
        autoTabIndex = (newChild._autoTabIndex || !newChild.tabIndex);

    if (isc.isA && isc.isA.Layout &&  autoTabIndex && 
        (newChild._canFocus() || (newChild.children != null && newChild.children.length > 0))) 
    {   
        var currentChild = newChild;
        while (currentChild.parentElement) {
            if (isc.isA.Layout(currentChild.parentElement) && currentChild.parentElement.isDrawn()) 
            {
                currentChild.parentElement.updateMemberTabIndex(currentChild);
                if (currentChild.parentElement == this) tabIndexManaged = true;
            }
            currentChild = currentChild.parentElement;
        }
    }

	// if we're not drawn yet, we'll wait to draw the child when we draw.  If we've been drawn,
    // tell the child to draw as well -- unless it has a master, in which case it's a peer of
    // another child, and that other child will draw it.
	if (this.isDrawn() && !newChild.masterElement) {
        //>DEBUG
        if (this.logIsDebugEnabled(this._$drawing)) {
            this.logInfo("child added to already drawn parent: " + 
                         (isc.Page.isLoaded() ? "page loaded, will draw immediately" :
                                            "page not loaded, will defer child drawing"),
                         "drawing");
        }   
        //<DEBUG
        
        // if the user has not specified a tabIndex for the child, slot it into the tab order
        // after the previous canFocus:true child for this widget, with an auto-allocated tab
        // index.
        //
        // Note: this widget is drawn, so we can assume that the tab index will have been 
        // assigned, and this._autoTabIndex will have been set to true if the tab index was 
        // auto-allocated.
        // We can assume the same for any focusable children of this widget.
        
        if (!tabIndexManaged && newChild._canFocus() && autoTabIndex) {
            
            var lastChild;
            
            if (this.children.length  > 1) {
                // find the previous child with an auto-allocated tab index
                for (var i = this.children.length -2; i >=0 ; i--) {
                    if (this.children[i]._canFocus() && this.children[i]._autoTabIndex) {
                        lastChild = this.children[i];
                        break;
                    }
                }
            }
            // if we didn't find a focusable (and _autoTabIndex) previous child, see if this widget
            // is focusable and has an auto-allocated tab index
            if (lastChild == null && this._canFocus() && this._autoTabIndex) {
                lastChild = this;
            }

            // slot this child after the last child with an auto-allocated tab index
            if (lastChild != null) newChild._setTabAfter(lastChild);
            
        }

        // Draw the child, and adjust overflow to account for any changes
        // NOTE: draw() may be delayed until after page load for some older browsers
             
        newChild.draw();
        this.adjustOverflow("addChild");
	}

	return newChild;
},


_updateChildrenTopElement : function () {
    var children = this.children;
    if (!children || children.length == 0) return;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        child.topElement = this.topElement;
        child._updateChildrenTopElement();
    }
},

//>	@method	canvas.reparent()
//		Make this canvas have the same parent as some other canvas.
//		Works even if this is a top-level object.
//
//		@return	(boolean)	true == reparenting actually occurred.
//		@group	containment
//<
reparent : function (newSibling) {

    // bail if we're trying to reparent to ourselves!
	if (this.getID() == newSibling.getID()) return false;

    // If they have the same ISC AND Native parents already, just bail
    if ((this.parentElement == newSibling.parentElement) &&
        this.getClipHandle() && newSibling.getClipHandle() &&
        (this.getClipHandle().parentNode == newSibling.getClipHandle().parentNode)) {
        return false;
    }
    
    // Note - to handle relative positioning, etc. properly, we need to ensure that this
    // widget ends up with the same DOM parent as the newSibling - regardless of whether it is
    // an ISC parentElement
    
    // Ensure this will be drawn next to the newSibling
    this._adjacentHandle = newSibling.getClipHandle();

	if (newSibling.parentElement) {
        // join our sibling's parent (will handle setting up ISC relationships, and drawing)
		newSibling.parentElement.addChild(this);
	} else {
        // Ditch any existing parent
		if (this.parentElement) this.deparent();
        // Or clear() so we can redraw
        else this.clear();
        
        // and draw anew next to the newSibling
		this.draw();
	}
	return true;
},

// NOTE: child/peer removal: 
// - you can call either deparent or removeChild to accomplish child removal (likewise
//   depeer/removePeer)
// - deparented/childRemoved and depeered/peerRemoved are clean notification points that are
//   guaranteed to be called
// - removePeer/removeChild are guaranteed to be called, so can be used as an override point in
//   advanced widgets.  deparent/depeer are *not* guaranteed to be called - with a set of flags we
//   could make this possible without infinite recursion

//>	@method	canvas.removePeer()
// Remove a peer from this Canvas
// @group containment
// @param peer (Canvas instance) Peer to be removed from this canvas
// @param [name] (string) If this peer was assigned a name it should be passed in here to ensure
//                      canvas[name] is cleared out
// @visibility external
//<
removePeer : function (peer, name) {
    if (peer == null) return;

    var peers = this.peers, index;
    //>DEBUG
    if (!peers || (index = peers.indexOf(peer)) == -1) {
        this.logWarn("Attempt to remove peer: " + peer + " from Canvas that is not its master");
        return;
    }
    //<DEBUG

    // remove our links to the peer
    peers.removeAt(index);
	if (this[name] == peer) this[name] = null;
    // remove peer's link to us
    peer.masterElement = null;
    // fire notifications
    if (peer.depeered) peer.depeered(this, name);
    if (this.peerRemoved) this.peerRemoved(peer, name);
},

//>	@method	canvas.depeer()
//	Make this Canvas no longer a peer of its master
//		@group	containment
//<
depeer : function (name) {
    if (!this.masterElement) return;
    this.masterElement.removePeer(this, name);
},

//>	@method	canvas.removeChild()
//	Remove a child from its parent if it has one.
//		@group	containment
// @param child (Canvas instance) Child canvas to remove from this parent.
// @param [name] (string) If the child canvas was assigned a name, it should be passed in here
//                          to ensure this[name] is cleared
// @visibility external
//<

removeChild : function (child, name) {
    if (isc._traceMarkers) arguments.__this = this;
    if (child == null) return;

    var children = this.children, index;
    //>DEBUG
    if (!children || (index = children.indexOf(child)) == -1) {
        this.logWarn("Attempt to remove child: " + child + " from Canvas that is not its parent");
        return;
    }
    //<DEBUG

    // remove our links to the child
    children.removeAt(index);
    if (this[name] == child) this[name] = null;
    // remove the child's HTML.  NOTE needs to happen before parentElement/topElement are
    // removed since clear() fires key notifications that cause the parent to adjust to the now
    // undrawn child.
	if (child.isDrawn()) child.clear();
    // remove child's links to us
	delete child.parentElement;
	delete child.topElement;
    
    // Add the child to the top level list of canvii
    isc.Canvas._addToTopLevelCanvasList(child);
    
	// deparent any peers of the child, which are also our children
	if (child.peers) child.peers.map("deparent");
    // fire notifications
    if (child.deparented) child.deparented(this, name);
    if (this.childRemoved) this.childRemoved(child, name);
},

//>	@method	canvas.deparent()
//	Remove this canvas from its parent if it has one.
//		@group	containment
//<
deparent : function (name) {
    if (!this.parentElement) return;
    this.parentElement.removeChild(this, name);
},


//>	@method	canvas.addPeer()    ([])
//      Adds newPeer as a peer of this widget (also making it a child of this widget's parent, if
//      any), set up a named object reference (i.e., this[name]) to the new widget if name is
//      provided, and draw the peer if this widget has been drawn already.<br>
//      The widget to be added as a peer will be removed from its old master and/or parent, if any,
//      and it will be added as a child to the parent of this canvas (if any)
//  @visibility external
//	@group	containment
//	@param	newPeer		(canvas)	new peer widget to add
//	@param	[name]		(string)	name to assign to peer (eg: this[peer] == child)
//  @param  [autoDraw]  (boolean)   if true, peer will not automatically be drawn (only
//                                  for advanced use)
//  @param  [preDraw] (boolean)
//                                  if true, when draw is called on the master widget, the peer
//                                  will be drawn before the master
//	@return	(canvas)	the new peer, or null if it couldn't be added
//<
addPeer : function (newPeer, name, autoDraw, preDraw) { 
	if (!newPeer) return null;	// just to be safe
	
    // instantiate the peer on the fly if it hasn't been created yet (autodraw is suppressed)
	if (!isc.isAn.Instance(newPeer)) newPeer = this.createCanvas(newPeer);

	// if this peer is marked for 'predrawing', hang the '_drawBeforeMaster' flag onto the peer, so
    // that when draw is called on the master, this peer gets drawn first.
    // If the master element is already drawn, and this flag is set, we'll call 'redraw' on the 
    // master element when this newPeer gets drawn (below).
    if (preDraw == true) newPeer._drawBeforeMaster = true;
	
    // if newPeer already recognizes this canvas as its master, bail
	if (newPeer.masterElement == this) return null;
	
	// remove the peer from its old master, if any
	if (newPeer.masterElement) newPeer.depeer(name);
	
	// attach the peer to its new master (this canvas)
	newPeer.masterElement = this;
	if (name) this[name] = newPeer;
	if (!this.peers) this.peers = [];
	// the following conditional allows for direct initialization of the peers[] property
	if (!this.peers.contains(newPeer)) this.peers.add(newPeer);

	// attach the peer to the same parent as us
	if (this.parentElement) {
		// make the peer a child of our parent (removes peer from its old parent, if any)
		this.parentElement.addChild(newPeer, name);
	} else if (newPeer.parentElement) {
		// or detach the peer from its old parent if it has one
		newPeer.deparent();
	}
    
    // If we're keeping our opacity in synch with that of our peers, update in now.
    if (newPeer._setOpacityWithMaster && (newPeer.opacity != this.opacity))
        newPeer.setOpacity(this.opacity);
        
    // If we're showing / hiding with our peers ensure peers visibility is in synch with ours
    if (newPeer._showWithMaster && (newPeer.visibility != this.visibility)) {
        newPeer.setVisibility(this.visibility);
    }
    
	// If snapTo or snapEdge are set, recalc peer position
	if (newPeer.snapTo || newPeer.snapEdge) newPeer._resolvePercentageSize();
		
    var EH = this.ns.EH;
    if (EH.clickMaskUp()) {
        var CMIDs = EH.getAllClickMaskIDs();
        for (var i = CMIDs.length -1; i >= 0; i--) {
            var masterMasked = EH.targetIsMasked(this, CMIDs[i]);
            if (!masterMasked) {
                // addUnmaskedTarget will automatically unmask children and peers of the
                // new peer recursively.
                EH.addUnmaskedTarget(newPeer, CMIDs[i]);
                // We're iterating down from the top - once a widget is over one mask it's also
                // over any masks below that one. Therefore we don't need to keep iterating 
                // down to the bottom adding unmasked targets.
                break;
            } else {
                // If we're masked but our peer is not, mask the peer
                var peerMasked = EH.targetIsMasked(newPeer, CMIDs[i]);
                if (!peerMasked) EH.maskTarget(newPeer, CMIDs[i]);
            }
        }
    }

    // for very advanced callers, support not drawing the child automatically
    if (autoDraw == false) return newPeer;

	// if we've been drawn and the peer hasn't, tell the peer to draw as well
	if (this.isDrawn() && !newPeer.isDrawn()) {
		newPeer.draw();
        // If the '_drawBeforeMaster' flag has been set on the new peer, force a redraw of the
        // master after the peer is first drawn.
        // We do this because the _drawBeforeMaster flag implies that the master element expects
        // the peer to have been drawn when it itself is drawn, for example, so it can make use
        // of the peer's drawn size in its own getInnerHTML() method.
        // This redraw therefore gives the master a chance to rebuild its HTML after the peer
        // has been drawn.
        if (newPeer._drawBeforeMaster) this.redraw();
	}

	return newPeer;
},

// SnapTo / SnapEdge positioning
// ---------------------------------------------------------------------------------------

//> @method canvas.setSnapTo()  ([])
// Set the snapTo property of this canvas, and handle repositioning.
//
// @group positioning
// @param snapTo (string) new snapTo value
// @visibility external
//<
setSnapTo : function (snapTo) {
    this.snapTo = snapTo;
    this.parentResized();
},

//> @method canvas.getSnapTo()  ([])
// Return the snapTo value of this object
//
// @return (string) snapTo
// @group positioning
// @visibility external
//<
getSnapTo : function () {
    return this.snapTo;
},

//> @method canvas.setSnapEdge()  ([])
// Set the snapEdge property of this canvas, and handle repositioning.
//
// @param snapEdge (string) new snapEdge value
// @group positioning
// @visibility external
//<
setSnapEdge : function (snapEdge) {
    this.snapEdge = snapEdge;
    this.parentResized();   
},

//> @method canvas.getSnapEdge()  ([])
// Return the snapEdge value of this object
//
// @return (string)    snapEdge
// @group  positioning
// @visibility external
//<
getSnapEdge : function () {
    return this.snapEdge;
},

//>EditMode
// provide addChild and removeChild as the adder/remover function for the "children" field (not
// mechanically guessable by naming conventions)
getFieldMethod : function (type, fieldName, verb) {
    //this.logWarn("getMethod, field: " + fieldName);
    if (fieldName == "children") {
        if (verb == "add") return "addChild";
        if (verb == "remove") return "removeChild";
    }
    return this.Super("getFieldMethod", arguments);
},
//<EditMode

// Canvas hierarchy
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getParentElements()	([A])
//      Returns an array of object references to all ancestors of this widget in the containment
//      hierarchy, starting with the direct parent and ending with the top element.
//  @visibility external
//  @group	containment
//  @return	(array)	array of parentElements, closest first; empty array if no parentElements
//<
getParentElements : function () {
	var list = [],
		parent = this.parentElement;
	// while there are parents
	while (parent) {
		// add them to the list
		list.add(parent);
		parent = parent.parentElement;
	}
	// return the list
	return list;
},

//>	@method	canvas.contains()	([A])
//      Returns true if element is a descendant of this widget (i.e., exists below this widget in
//      the containment hierarchy); and false otherwise.
//  @visibility external
//  @group	containment
//	@param	canvas	(canvas)    the canvas to be tested
//  @param  [testSelf] (boolean) If passed this method will return true if the the canvas 
//                               parameter is a pointer to this widget.
//	@return	(boolean)	true if specified element is a descendant of this canvas; false otherwise
//<
contains : function (canvas, testSelf) {
    if (!testSelf && canvas) canvas = canvas.parentElement;
	while (canvas) {
		if (canvas == this) return true;
		canvas = canvas.parentElement;
	}
	return false;
},

// Is this element the parent of the child passed in, AND the child inherits its visibility from
// this parent?
_isVisibilityAncestorOf : function (child) {
    var target = child;

    while (target) {
        if (target == this) return true;
        var inherits = (target.visibility == isc.Canvas.INHERIT);
        if (!inherits) return false;
        target = target.parentElement;
    }
    return false;
},

// get total number of recursively contained children
getChildCount : function () {
    if (this.children == null) return;
    return this.children.map("getChildCount").sum() + this.children.length;
},


// ClickMask
// --------------------------------------------------------------------------------------------

//>	@method Canvas.showClickMask()
// Show a clickMask over the entire screen that intercepts mouse clicks and fires some action.
// The mask created will be associated with this canvas - calling this method multiple times
// will not show multiple (stacked) clickMasks if the mask associated with this canvas is 
// already up.<br><br>
//
// The clickMask useful for modal dialogs, menus and similar uses, where any click outside of
// some Canvas should either be suppressed (as in a modal dialog) or just cause something (like
// dismissing a menu). 
// 
// @group	clickMask
//
// @param	clickAction	    (callback)	action to fire when the user clicks on the mask
// @param	mode        (clickMaskMode)	whether to automatically hide the clickMask on mouseDown
//                                      and suppress the mouseDown event from reaching
//                                      the target under the mouse
// @param   unmaskedTargets (widget | array of widgets) 
//  initially unmasked targets for this clickMask. Note that if this is a
//  <code>"hard"</code> mask, unmasked children of masked parents are not supported
//  so any non-top-level widgets passed in will have their parents unmasked.
//  Children of masked parents can never be masked.
// @return  (string)    clickMask ID 
// @see     canvas.hideClickMask()
// @visibility external
//<
showClickMask : function (clickAction, mode, unmaskedTargets) {
    
    var ID = this.getID();
    if (!this.ns.EH.clickMaskUp(ID)) {    
        return this.ns.EH.showClickMask(clickAction, mode, unmaskedTargets, ID);
    }
},

//>	@method	Canvas.hideClickMask()
// Hides the click mask associated with this canvas.
//		@group	clickMask
//      @param  [ID]    (string) optional ID of specific clickMask to hide. If not passed, 
//                      defaults to hiding the click mask associated with this widget only.
//      @visibility external
//      @see canvas.showClickMask()
//<
hideClickMask : function (ID) {
    if (ID == null) ID = this.getID();
    if (this.ns.EH.clickMaskUp(ID)) this.ns.EH.hideClickMask(ID);
},


//>	@method	Canvas.clickMaskUp()
// Determines whether a clickmask is showing
//		@group	clickMask
//      @param  [ID]    (string) optional ID of specific clickMask to check. If not passed, 
//                      checks for the click mask associated with this widget only.
//      @visibility external
//      @see canvas.showClickMask()
//<
clickMaskUp : function (ID) {
    if (ID == null) ID = this.getID();
    return this.ns.EH.clickMaskUp(ID);
},


//>	@method	Canvas.unmask()
// If a click mask is currently covering this widget, unmask it.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask for which this widget should 
//              be unmasked. If not passed, unmasks target wrt all clickMasks.
// @visibility clickMask
//<
unmask : function (mask) {
    this.ns.EH.addUnmaskedTarget(this, mask);
},

//>	@method	Canvas.mask()
// Ensure this widget is obscured by a currently visible clickMask.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask to put this widget behind.
//                            If not passed, masks target wrt all clickMasks.
// @visibility clickMask
//<
mask : function (mask) {
    this.ns.EH.maskTarget(this, mask);
},

//>	@method	Canvas.isMasked()
// Is this widget currently obscured by a currently visible clickMask.
// @group	clickMask
// @param  [mask]    (string) optional ID of specific clickMask to test. If not passed, will
//                          return true if this canvas is masked by any visible clickMask.
// @visibility clickMask
//<
isMasked : function (mask) {
    return this.ns.EH.targetIsMasked(this, mask);
},

// Helper method - are we covered by a hard (auto-hide:false) clickMask?
_isHardMasked : function () {
    var masks = isc.EH.clickMaskRegistry;
    if (!masks || masks.length == 0) return false;

    for (var i = masks.length-1; i >= 0; i--) {
        var mask = masks[i];
        // If we're unmasked and haven't already hit a hard mask, we're not hard masked
        if (!this.isMasked(mask)) return false;
        // If we hit a hard mask, we are hard masked
        if (isc.EH.isHardMask(mask)) return true;
    }
    // In this case we didn't hit a hard mask, so any masks above us must be soft.
    return false;
},

// Component level masking
// ----------------------------------------------------------------------------------------------
// Support for masking children of this widget only
showComponentMask : function (maskProperties) {
    if (!this.componentMask) {
        this.componentMask = this.addAutoChild(
            "componentMask",
             // mark as disabled - automatically will kill events and not allow bubbling
             isc.addProperties({}, maskProperties, {disabled:true,
             autoDraw:false,
             // resizeWithMaster / moveWithMaster will be true by default
             _setOpacityWithMaster:false
            }), 
            isc.Canvas
        );
        this.componentMask.setRect(this.getOffsetLeft(), this.getOffsetTop(),
                                   this.getVisibleWidth(), this.getVisibleHeight());
        this.addPeer(this.componentMask);
    } else if (!this.componentMask.isDrawn()) this.componentMask.draw();
    
    this.disableKeyboardEvents(true, true);
},

// Enable this to make the component mask visible by default
/*
componentMaskDefaults:{
    backgroundColor:"black",
    opacity:20
},
*/

hideComponentMask : function () {
    if (this.componentMask) this.componentMask.clear();
    this.disableKeyboardEvents(false, true);
},


// Widget Positioning and Sizing Methods
// --------------------------------------------------------------------------------------------
// Note on positioning coordinate systems:
//
//  When describing left / top positions of widgets, there are a few distinct possibilities for 
//  the coordinate system you're referring to:
//  1 - Specified widget coordinates 
//      - left/top (at init time), getLeft()/getTop(), setLeft()/setTop()
//      For absolutely positioned widgets, this is the distance from the top/left of this 
//      widget (measured from outside any border or margin) to the inside of the parent's 
//      content.
//      For relatively positioned widgets it is the offset relative to page flow within
//      this widget's parent element.
//  2 - Page level coordinates (getPageLeft() and getPageTop()).
//      This is the absolute offset of the widget from the top / left of the browser window, 
//      measured from outside the widget's border and margin.
//      Will match getLeft() / getTop() for absolutely positioned elements at the top level.
//  3 - Canvas level coordinates (getCanvasLeft() / getCanvasTop())
//      This is the absolute offset of the widget from the left / top of its 'parentElement' - 
//      the ISC widget defined as it's parent.  Measured from the outside of any border/margin
//      on this widget to the inside of the parent widget's handle - so for
//      absolutely positioned elements will be the same as the specified widget coordinates,
//      and in almsot every case will be identical to the result of getOffsetLeft() / top()
//      [As the parent scrolls, this value will not change, like the specified or offset values
//       it is relative to the parent's content rather than floating position on the page].
//  4 - Offset coordinates (getOffsetLeft() and getOffsetTop()).
//      This is the absolute offset of the widget from the left / top of the native DOM 
//      offsetParent of the widget (may or may not be a canvas).
//      Value is calculated from the outside of any border / margin of this widget to the 
//      inside edge of the offsetParent element.
//      Used internally - should not need to be exposed.


//>	@method	canvas.setRect()    ([])
// Set all four coordinates, relative to the enclosing context, at once.
// <P>
// Moves the widget so that its top-left corner is at the specified top-left coordinates,
// then resizes it to the specified width and height.
//
//      @visibility external
//		@group	positioning, sizing
//		@param	[left]		(number, Array, Object)	new left coordinate, Array of coordinates
//                                                  in parameter order, or Object with left,
//                                                  top, width, height properties.  If an Array
//                                                  or Object is passed, the remaining
//                                                  parameters are ignored
//		@param	[top]		(number)	new top coordinate
//		@param	[width]		(number)	new width
//		@param	[height]	(number)	new height
//      @return (boolean) whether the component's size actually changed
//<
//>Animation
// @param [animating] (boolean) Internal optional parameter passed if we are performing
//  an animated setRect
//<Animation
setRect : function (left, top, width, height, animating) {
    
    if (isc._traceMarkers) arguments.__this = this;
	if (isc.isAn.Array(left)) {
		top = left[1];
		width = left[2];
		height = left[3];
		left = left[0];
	} else if (left != null && left.top != null) {
        top = left.top;
        width = left.width;
        height = left.height;
        left = left.left;
    }
	
	
    //>DEBUG
    if (this.logIsDebugEnabled()) {
        this.logDebug("setRect: " + this.echo({left:left, top:top, width:width, height:height}));
    }
    //<DEBUG

    
    
    
    
	// first resize its width and height
    
 	var sizeChanged = this.resizeTo(width, height, animating, true);
    
    if (sizeChanged) this._settingRect = true;
	// now move the canvas
    
	this.moveTo(left, top, animating, true);
    this._settingRect = null;
    return sizeChanged;
},


//>	@method	canvas.getRect()
//			return the coordinates of this object as rendered in LTWH order
//		@group	positioning, sizing
//		
//		@return	(array)		[left, top, width, height]
//<
getRect : function () {
	return [this.getLeft(), this.getTop(), this.getVisibleWidth(), this.getVisibleHeight()];
},

//>	@method	canvas.getLeft()    ([])
//			Return the left coordinate of this object, relative to its enclosing context, in pixels.
//      @visibility external
//		@group	positioning
//		@return	(number)	left coordinate
//<
getLeft : function () {
    var handle = this.getStyleHandle();
    // it hasn't been drawn yet - return this.left
    if (handle == null) return this.left;    
    var left = (isc.Browser.isIE ? handle.pixelLeft : parseInt(handle.left));
 
    
        
        if (this.vscrollOn && this.showCustomScrollbars && this.isRTL()) {
            return left - this.getScrollbarSize();
        }
        return left;
    
    
},

//>	@method	canvas.getOffsetLeft()
//			Return the offsetLeft coordinate of this object, 
//          relative to its (ISC) parent, in pixels.
//		@group	positioning
//
//		@return	(number)	left coordinate
//<
getOffsetLeft : function () {

    // This function returns the absolute position of widgets relative to their clipHandle's
    // offset parent (may be an ISC widget, but could be another HTML element too).
    

    // in this case we're always working with the clipHandle
    var handle = this.getClipHandle();
    
    
    if (isc.Browser.isMoz && this._isDisplayNone()) handle = null;

    // if we can't get the clip handle, just return the specified left coordinate
    if (handle == null) {
        //>DEBUG NOTE: not logging at WARN priority because it's just too common to manipulate
        // coordinates of an absolutely positioned widget before drawing it.
        if (this.logIsInfoEnabled()) {
            this.logInfo("getOffsetLeft() called before widget is drawn - unable to calculate offset " +
                         "coordinates.  Returning specified coordinates"); //<DEBUG
        }
        return this.left;
    }
        
    // just return the offsetLeft - this is the absolute position within logical parent element
    // ("offsetParent");
    var offsetLeft = isc.Element.getOffsetLeft(handle);
    if (this.vscrollOn && this.showCustomScrollbars && this.isRTL()) {
        offsetLeft -= this.getScrollbarSize();
    }
            
    
        return offsetLeft;
    
},

//>	@method	canvas.setLeft()    ([])
//			Set the left coordinate of this object, relative to its enclosing context, in pixels.
//			NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
//          instead
//      @visibility external
//		@group	positioning
//		@param	left		(number)	new left coordinate
//<
setLeft : function (left) {
	this.moveTo(left, null);
},

//>	@method	canvas.getTop() ([])
//			Return the top coordinate of this object, relative to its enclosing context, in pixels.
//      @visibility external
//		@group	positioning
//      @return (number)    top coordinate
//<
getTop : function () {
    var handle = this.getStyleHandle();
    if (handle == null) return this.top;

    var top = (isc.Browser.isIE ? handle.pixelTop : parseInt(handle.top));
    
        return top;
    
},

//>	@method	canvas.getOffsetTop()
//			Return the offsetTop coordinate of this object, 
//          relative to its (ISC) parent, in pixels.
//		@group	positioning
//
//		@return	(number)	top coordinate
//<
getOffsetTop : function () {

    // in this case we're always working with the clipHandle
    var handle = this.getClipHandle();
    
    
    if (isc.Browser.isMoz && this._isDisplayNone()) handle = null;

    // if we can't get the clip handle, return the specified top
    if (handle == null) return this.top;    
    
    // just return the offsetTop - this is the absolute position
    var top = isc.Element.getOffsetTop(handle);
    
    

    
        return top;
    
},

//>	@method	canvas.setTop() ([])
// Set the top coordinate of this object, relative to its enclosing context, in pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect() or moveTo() instead
//
//      @visibility external
//		@group	positioning
//		@param	top		(number)	new top coordinate
//<
setTop : function (top) {
	this.moveTo(null, top);
},


//>	@method	canvas.getWidth()   ([])
// Return the width of this object, in pixels.
//      @visibility external
//		@group	sizing
//		@return	(number)	width
//<
getWidth : function () {
	return this.width;
},


//>	@method	canvas.setWidth()   ([])
// Resizes the widget horizontally to the specified width (moves the right side of the
// widget). The width parameter can be expressed as a percentage of viewport size or as
// the number of pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use resizeTo() or setRect() instead
//    
// @visibility external
//		@group	sizing
//
//		@param	width		(number)	new width
//<
setWidth : function (width) {
	this.resizeTo(width);
},


//>	@method	canvas.getHeight()  ([])
// Return the height of this object, in pixels.
//      @visibility external
//		@group	sizing
//		@return	(number)	height
//<
getHeight : function () {
	return this._height;
},


//>	@method	canvas.setHeight()  ([])
// Resizes the widget vertically to the specified height (moves the bottom side of the
// widget). The height parameter can be expressed as a percentage of viewport size or as
// the number of pixels.
// <P>
// NOTE: if you're setting multiple coordinates, use resizeTo() or setRect() instead
//
//      @visibility external
//		@group	sizing
//		@param	height		(number)	new height
//<
setHeight : function (height) {
	this.resizeTo(null, height);
},

//>	@method	canvas.getMinWidth()
// Get the minimum width that this Canvas can be resized to.
//		@group	sizing
//		@return	(number)	width
//<
getMinWidth : function () {
	return this.minWidth;
},

//>	@method	canvas.getMinHeight()
// Get the minimum height that this Canvas can be resized to.
//		@group	sizing
//		@return	(number)	height
//<
getMinHeight : function () {
	return this.minHeight;
},

//>	@method	canvas.getMaxWidth()
// Get the maximum width that this Canvas can be resized to.
//		@group	sizing
//		@return	(number)	width
//<
getMaxWidth : function () {
	return this.maxWidth;
},

//>	@method	canvas.getMaxHeight()
// Get the maximum height that this Canvas can be resized to.
//		@group	sizing
//		@return	(number)	height
//<
getMaxHeight : function () {
	return this.maxHeight;
},

//>	@method	canvas.getRight()   ([])
// Return the right coordinate of this object as rendered, relative to its enclosing context,
// in pixels.
//
//      @visibility external
//		@group	positioning, sizing
//		@return	(number)	right coordinate
//<
getRight : function () {
	return this.getLeft() + this.getVisibleWidth();
},


//>	@method	canvas.setRight()   ([])
// Resizes the widget horizontally to position its right side at the specified coordinate.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
// instead
//      @visibility external
//		@group	sizing
//		@param	right		(number)	new right coordinate
//<
setRight : function (right) {
	if (isc.isA.Number(right)) {
	    this.resizeTo(right - this.getLeft(), null);
	} else {
	    this.logWarn("setRight() expects an integer value");
	}
},


//>	@method	canvas.getBottom()  ([])
// Return the bottom coordinate of this object as rendered, relative to its enclosing context,
// in pixels.
//
//      @visibility external
//		@group	positioning, sizing
//		@return	(number)	bottom coordinate
//<
getBottom : function () {
	return this.getTop() + this.getVisibleHeight();
},


//>	@method	canvas.setBottom()  ([])
// Resizes the widget vertically to position its bottom edge at the specified coordinate.
// <P>
// NOTE: if you're setting multiple coordinates, use setRect(), moveTo() or resizeTo()
// instead
//
//      @visibility external
//		@group	sizing
//		@param	bottom		(number)	new bottom coordinate
//<
setBottom : function (bottom) {
	if (isc.isA.Number(bottom)) {
	    this.resizeTo(null, bottom - this.getTop());
	} else {
	    this.logWarn("setBottom() expects an integer value");	
	}
},

// Enforcing scroll size and "virtual content"
// Consider the following use cases:
// - A Layout containing a layout-spacer as its last member
// - A parent with a number of children, and the parent wants to create "padding" around 
//   those children that behaves like CSS padding.
//   CSS padding as such can't be used because it doesn't affect the positioning of absPos 
//   children, and does not wrap around absPos children.
//   The Layout class encounters this use case with the 'layoutMargin' property.
//
// In these cases we have "virtual content" - we know the size we intend the widget's content
// to be, but the browser does not recognize this content and *will not scroll to it*.
// 
// We use the enforceScrollSize() / stopEnforcingScrollSize() methods below to workaround this
// issue. When enforceScrollSize() is called, we write an absolutely positioned DIV into the 
// widget handle after all other content, giving the handle a truly scrollable area.
// Currently we only make use of these methods in the Layout class if layoutMargin is set
// or the last member is a layoutSpacer.
// We may want to generalize this the Canvas class, for example having a flag that
// automatically calls the 'enforceScrollSize()' on addChild(), childMoved(), childResized
// but we don't have a use-case where this is required at present.

//> @method canvas.enforceScrollSize ()
// Ensure that this widget's scrollable area matches (or exceeds) the dimensions passed in
// @visibility internal
// @param width (number) scroll width
// @param height (number) scroll height
// @see canvas.stopEnforcingScrollSize()
//<

_scrollSizeDivTemplate:["<DIV ID='",
                        null,   // 1: ID
                        "'style='position:absolute;width:1px;height:1px;overflow:hidden;left:", 
                        null,   // 3: left
                        "px;top:",
                        null,   // 5: top
                        "px;'>&nbsp;</DIV>"],
_$scrollSizeDiv:"scrollSizeDiv",    
//>DEBUG
_$enforceScrollSize:"enforceScrollSize",
//<DEBUG                    
enforceScrollSize : function (width, height) {
    //>DEBUG
    if(this.logIsDebugEnabled(this._$enforceScrollSize)) {
        this.logDebug("enforcing scroll size:"+ [width, height], "enforceScrollSize");
    }
    //<DEBUG

    if (!this._handleDrawn && !this._drawn) return;
    
    if (width == null) width = 0;
    if (height == null) height = 0;

    // partial fix/workaround for INFA issue #1857
    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
        this.logWarn("Invalid width or height in Canvas.enforceScrollSize()"
                    +" on component: " + this.getID() + " with sizes: "
                    + [width, height] + this.getStackTrace());    
        return;
    }
        
    if (this._scrollSizeDiv == null) {
        var template = this._scrollSizeDivTemplate;
        var name = this._getDOMID(this._$scrollSizeDiv);
        template[1] = name;
        template[3] = width-1; 
        template[5] = height-1;
        
        var HTML = template.join(isc.emptyString);
        // We clear this pointer on clear()
        // We also handle redraw
        this._scrollSizeDiv = 
            isc.Element.insertAdjacentHTML(this.getHandle(), this._$beforeEnd, HTML, true);
        if (this._scrollSizeDiv == null) {
            this._scrollSizeDiv = document.getElementById(name);
        }            
    } else if (!this._enforcingScrollSize || this._enforcingScrollSize[0] != width ||
                this._enforcingScrollSize[1] != height) 
    {
        this._scrollSizeDiv.style.left = (width-1) + isc.px;
        this._scrollSizeDiv.style.top = (height-1) + isc.px;
    }    
    this._enforcingScrollSize = [width,height];
},

_$minus1px:"-1px",
stopEnforcingScrollSize : function () {
    //>DEBUG
    if(this.logIsDebugEnabled(this._$enforceScrollSize)) {
        this.logDebug("stop enforcing scroll size", "enforceScrollSize");
    }
    //<DEBUG        
    
    delete this._enforcingScrollSize;
    if (!this.isDrawn()) return;
    
    if (this._scrollSizeDiv) {
        this._scrollSizeDiv.style.left = this._$minus1px;
        this._scrollSizeDiv.style.top = this._$minus1px;
    }
},




//>	@method	canvas.getScrollWidth() ([A])
// Returns the scrollable width of the widget's contents, including children, ignoring
// clipping.
//      @visibility external
//		@group	sizing
//
//		@return	(number)	the scrollable width of the widget's contents
//<
getScrollWidth : function (calculateNewValue) {
    if (isc._traceMarkers) arguments.__this = this;
     
    if (this._deferredOverflow) {
        this._deferredOverflow = null;
        this.adjustOverflow("widthCheckWhileDeferred");
    }

    // if we have a cached value and we're not looking for a fresh one, dont calculate a
    // new one
    if (!calculateNewValue && this._scrollWidth != null) return this._scrollWidth;
    
    var width = 0,
        handle = this.getClipHandle();

    if (handle == null) {
        //>DEBUG
        this.logDebug("No size info available from DOM, returning user-specified size");
        //<DEBUG
        return this.getInnerWidth();
    }

    // allowNativeContentPositioning: special flag for when our handle's HTML may include 
    // absolutely positioned HTML child nodes.
    if (this.allowNativeContentPositioning) {
        
        this._retrievingScrollWidth = true;
        
        
        if (isc.Browser.isSafari ||
            ( isc.Browser.isMoz && 
              ((handle.scrollWidth || handle.offsetWidth) <= parseInt(handle.style.width)) ) ) 
        {
            width = isc.Element.getScrollWidth(this.getHandle());
        } else {
            width = isc.Element.getScrollWidth(handle);
        }
            
        delete this._retrievingScrollWidth;
        
    } else { 
        // simple content - worry only about explicitly specified ISC children, and the
        // reported scrollHeight / width
        
        var children = this.children,
            hasChildren = children && children.length > 0,
            handleScrollWidth = 0;
        
        // If have content, look at the clip handle's reported scroll size.
        if (!hasChildren || this.allowContentAndChildren) {
            
            if (isc.Browser.isSafari && this.overflow == isc.Canvas.VISIBLE) {
                width = this.getHandle().scrollWidth;
                
                if (this.useClipDiv && this.padding == null) {
                    width += isc.Element._getHPadding(this.styleName);
                }
            } else {
    
                handleScrollWidth = (handle.scrollWidth || handle.offsetWidth);
                
                // use this scrollWidth if it's available
                if (handleScrollWidth != null && handleScrollWidth != this._$undefined) {
                    width = handleScrollWidth;
    
                    // Opera adds left padding and left border
                    if (isc.Browser.isOpera) {
                        
                        width -= (this.getLeftBorderSize() + this.getLeftPadding());
                    }
    
                    // account for explicit children at negative coords in Moz                    
                    if (isc.Browser.isMoz) width -= this._offscreenChildrenWidth();
    
                    
                    if (isc.Browser.isMoz && 
                        this.getScrollingMechanism() == isc.Canvas.NESTED_DIV) 
                    {
                        var offsetAdjustment = this.getHandle().offsetLeft;
                        if (offsetAdjustment < 0) offsetAdjustment = -offsetAdjustment;    
                        width -= offsetAdjustment;
                    }
                        
                } 
                
                
                if (isc.Browser.isSafari || 
                    (isc.Browser.isMoz && width <= parseInt(handle.style.width))) 
                {
                    var contentHandle = this.getHandle(),
                        contentWidth = contentHandle.scrollWidth || contentHandle.offsetWidth;
                    if (contentWidth > width) width = contentWidth;
                    
                    /*
                    var contentHandle = this.getHandle(),
                        contentWidth = contentHandle.scrollWidth || contentHandle.offsetWidth;
                    if (contentWidth < width && 
                        (this.padding != null || (width - contentWidth) > this.getHPadding())) 
                    {
                        this.logWarn("using contentWidth of: " + contentWidth + 
                                     " instead of scrollWidth of: " + width + 
                                     ", hPad: " + this.getHPadding());
                        width = contentWidth;
                    }
                    */
                }

            }

        } // end of check for native handle scrollWidth
        
        
        if (hasChildren) {
            var childrenWidth = this._getWidthSpan(this.children);
            width = Math.max(childrenWidth, width);
            
            // if we're enforcing scroll size, explicitly respect that
            // Note if we have content and children we should explicitly pick this up in the
            // reported scroll width of the handle - this is for the case where we have 
            // children only so never measure the handle
            if (this._enforcingScrollSize != null) {
                var enforcedWidth = this._enforcingScrollSize[0];
                width = Math.max(width, enforcedWidth);
            }
        }
     
    }
    //if (this.containsIFrame()) {
    //    this.logWarn("Normal scrollWidth: " + width + 
    //                 ", IFrame scrollWidth of: " + this._getIFrameScrollWidth());
        //return this._scrollWidth = this._getIFrameScrollWidth();
    //}

    // cache the scrollwidth to speed up future calls to this method.
    this._scrollWidth = width;
    return width;
},

// get the distance from the furthest left to the furthest right in a list of widgets

_getWidthSpan : function (children, skipHidden) {
    var mostLeft = 0, mostRight = 0,
        horizontalOverflow = this.overflow == isc.Canvas.VISIBLE ||
                             this.overflow == isc.Canvas.CLIP_H,
        mostRightChild;
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        
        
        //if (!isc.isA.Canvas(child)) continue;
        if (!child.isDrawn() && !child._hasUndrawnSize) continue;
        if (skipHidden && child.visibility == isc.Canvas.HIDDEN) continue;
                        
        var isAbsolute = (child.position != isc.Canvas.RELATIVE), 
            childWidth = child.getVisibleWidth(),
            childLeft = (isAbsolute ? child.getLeft() : child.getOffsetLeft());

        // Natively we can't scroll to view right/bottom margins of absolute elements, but we
        // can for relative/inline elements.
        // When calculating the scrollwidth of a scrollable widget, don't include the 
        // right-margin of absolute children.
        // Note - we don't make this adjustment if the overflow is visible on the horizontal
        // axis as we do want the widget to expand to accomodate the child's margin on both 
        // sides
        if (!horizontalOverflow && isAbsolute) childWidth -= child.getRightMargin();
    
        // NOTE: DO use negative coordinate to reduce rightward extent..
        if (childLeft + childWidth > mostRight) {
            mostRight = childLeft + childWidth;
            mostRightChild = child;
        }
        // .. but don't report negative extents as part of span
        // Exception: In RTL mode (in IE) abs-positioned widgets with negative left coordinates
        // can be reached by scrolling (even though the reported scroll position uses coords from
        // zero to the total scrollWidth).nm
        if (childLeft < mostLeft) mostLeft = this.isRTL() ? childLeft : Math.max(0,childLeft);
    }
    //if (isc.isA.Window(this)) this.logWarn("most right child: " + mostRightChild);
    return mostRight - mostLeft;
},


//>	@method	canvas.getScrollHeight()    ([A])
//			Returns the scrollable height of the widget's contents, including children, ignoring
//          clipping.
//      @visibility external
//		@group	sizing
//
//		@return	(number)	height of the element that can scroll
//<
getScrollHeight : function (calculateNewValue) {
    if (isc._traceMarkers) arguments.__this = this;
     
    if (this._deferredOverflow) {
        this._deferredOverflow = null;
        this.adjustOverflow("heightCheckWhileDeferred");
    }
    
    // If we've already cached the value, return it.
    if (!calculateNewValue && this._scrollHeight != null) return this._scrollHeight;

    var height = 0,
        handle = this.getClipHandle();
    
    if (handle == null) {
        //>DEBUG
        this.logDebug("No size info available from DOM, returning user-specified size");
        //<DEBUG
        return this.getInnerHeight();
    }

    if (this.allowNativeContentPositioning) {
        
        this._retrievingScrollHeight = true;

        
        if (isc.Browser.isSafari ||
            (isc.Browser.isMoz && 
            ((handle.scrollHeight || handle.offsetHeight) <= parseInt(handle.style.height))) ) 
        {
             height = isc.Element.getScrollHeight(this.getHandle());
        } else {
             height = isc.Element.getScrollHeight(handle);
        }
            
        delete this._retrievingScrollHeight;
    } else {
    
        // simple content - worry only about explicitly specified ISC children, and the
        // reported scrollHeight / width
        //this.logWarn("handle.scrollHeight: " + this.getHandle().scrollHeight + 
        //             ", handle.offsetHeight: " + this.getHandle().offsetHeight +
        //             ", clipHandle.scrollHeight: " + this.getClipHandle().scrollHeight +
        //             ", clipHandle.offsetHeight: " + this.getClipHandle().offsetHeight);
        
        var hasChildren = (this.children && this.children.length > 0);
        if (!hasChildren || this.allowContentAndChildren) {
            
            if (isc.Browser.isSafari && this.overflow == isc.Canvas.VISIBLE) {
                height = this.getHandle().scrollHeight;
                
                if (this.useClipDiv && this.padding == null) {
                    height += isc.Element._getVPadding(this.styleName);
                }
            } else {

                var scrollHeight = (handle.scrollHeight || handle.offsetHeight);
        
                // use this scrollHeight if it's available
                if (scrollHeight != null && scrollHeight != this._$undefined) {
                    height = scrollHeight;
                    if (isc.Browser.isMoz) height -= this._offscreenChildrenHeight();
    
                    // Opera incorrectly includes top border
                    //if (isc.Browser.isOpera) height -= this.getTopBorderSize();
                    
                    
                    if (this.useClipDiv && 
                        (isc.Browser.isSafari || 
                         (isc.Browser.isMoz && height <= parseInt(handle.style.height)))) 
                    {
                        var contentHandle = this.getHandle(),
                            contentHandleHeight = contentHandle.scrollHeight ||
                                                        contentHandle.offsetHeight;
                        if (contentHandleHeight > height) height = contentHandleHeight;
                    }
                }
                
            }
        }

        
        if (hasChildren) {
            
            var childrenHeight = this._getHeightSpan(this.children);
            //this.logWarn("handleHeight: " + height + ", childrenHeight: " + childrenHeight);
            if (childrenHeight > height) {
                height = childrenHeight;
            }
            
            // as with scrollWidth, if we're enforcing scroll size, explicitly respect that
            if (this._enforcingScrollSize != null) {
                var enforcedHeight = this._enforcingScrollSize[1];
                height = Math.max(height, enforcedHeight);
            }
        }
    }

    //if (this.containsIFrame()) {
    //    this.logWarn("Normal scrollHeight of: " + height +
    //                 ", IFrame scrollHeight of: " + this._getIFrameScrollHeight());
    //    //return this._scrollHeight = this._getIFrameScrollHeight();
    //}
            
    // cache the value to speed up future returns
    this._scrollHeight = height;
    return height;
},


_offscreenChildrenHeight : function () {
    if (!isc.isAn.Array(this.children)) return 0;
    var furthestNegative = 0;
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i],
            childTop = (child.position == isc.Canvas.ABSOLUTE ? 
                        child.getTop() : child.getOffsetTop());

        
        if (childTop < furthestNegative) furthestNegative = childTop;
    }
    //if (furthestNegative < 0) this.logWarn("offscreenHeight: " + furthestNegative);
    return -furthestNegative;
},
_offscreenChildrenWidth : function () {
    if (!isc.isAn.Array(this.children)) return 0;

    // doesn't happen for width axis with single div structure
    if (!this.useClipDiv) return 0;

    var furthestNegative = 0;
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i],
            childLeft = (child.position == isc.Canvas.ABSOLUTE ? 
                         child.getLeft() : child.getOffsetLeft());
        if (childLeft < furthestNegative) furthestNegative = childLeft;
    }
    //if (furthestNegative < 0) this.logWarn("offscreenWidth : " + furthestNegative);
    return -furthestNegative;
},

// get the distance from the furthest up to the furthest down for a list of widgets

_getHeightSpan : function (children, skipHidden) {
    var mostUp = 0, mostDown = 0,
        verticalOverflow = this.overflow == isc.Canvas.VISIBLE || 
                           this.overflow == isc.Canvas.CLIP_H;

    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        
        //if (!isc.isA.Canvas(child)) continue;
        
        // Always skip undrawn children - they should never impact scrollSize of a drawn parent
        // Support a flag to avoid this behavior. This is useful for LayoutSpacers which
        // are never drawn
        if (!child.isDrawn() && !child._hasUndrawnSize) continue;
        // hidden children will effect native scrollHeight so include them unless the
        // explicit skipHidden parameter was passed in (required for layouts)
        if (skipHidden && child.visibility == isc.Canvas.HIDDEN) continue;
                        
        var isAbsolute = child.position != isc.Canvas.RELATIVE,
            childHeight = child.getVisibleHeight(),
            childTop = (isAbsolute ? child.getTop() : child.getOffsetTop());

        // Natively we can't scroll to view right/bottom margins of absolute elements, but we
        // can for relative/inline elements.
        // When calculating the scrollHeight of a scrollable widget, don't include the 
        // bottom-margin of absolute children.
        // Note - we don't make this adjustment if the overflow is visible on the vertical
        // axis as we do want the widget to expand to accomodate the child's margin on both 
        // sides        
        if (!verticalOverflow && isAbsolute) childHeight -= child.getBottomMargin();

        // NOTE: DO use negative coordinate to reduce downward extent..
        if (childHeight + childTop > mostDown) mostDown = childHeight + childTop;
        // .. but don't report negative extents as part of span
        if (childTop < mostUp) mostUp = Math.max(0,childTop);
    }
    //this.logWarn("mostUp: " + mostUp + ", mostDown: " + mostDown);
    return mostDown - mostUp;
},



//>	@method	canvas.getScrollLeft()	(A)
// Get the number of pixels this Canvas is scrolled from its left edge.
//		@group	positioning, scrolling
//
//		@return	(number)	scrollLeft
// @visibility external
//<
getScrollLeft : function () {
    // if we're using synthetic scrolling, return our number
	if (!this.isDrawn() || this.getScrollingMechanism() != isc.Canvas.NATIVE) {
		return this.scrollLeft;
	}
    
	

    return this.getScrollHandle().scrollLeft;
},


//>	@method	canvas.getScrollTop()	(A)
// Get the number of pixels this Canvas is scrolled from its top edge.
//		@group	positioning, scrolling
//
//		@return	(number)	scrollTop
// @visibility external
//<
getScrollTop : function () {
    // if we're using synthetic scrolling, return our number
	if (!this.isDrawn() || this.getScrollingMechanism() != isc.Canvas.NATIVE) {
        return this.scrollTop;
    }
    // otherwise return whatever the browser reports
	return this.getScrollHandle().scrollTop;
},

// XXX setPageLeft/Top don't support percent

//> @method canvas.setPageLeft()
// Set the page-relative left coordinate of this widget.
// 
// @param left (number) new left coordinate in pixels
// @group positioning
// @visibility external
//<
setPageLeft : function (left) {
    this.moveBy(left - this.getPageLeft(), 0);
},

//> @method canvas.setPageTop()
// Set the page-relative top coordinate of this widget.
// 
// @param top (number) new top coordinate in pixels
// @group positioning
// @visibility external
//<
setPageTop : function (top) {
    this.moveBy(0, top - this.getPageTop());
},

// return the rect of this element's parent, or of the page if this element has no parent

getParentPageRect : function () {
	if (this.parentElement) {
        var parent = this.parentElement,
            rect = parent.getPageRect();

        // don't allow keepInParentRect widgets to go over margins
        
        var lMargin = parent.getLeftMargin(),
            tMargin = parent.getTopMargin();
        rect[0] += lMargin;
        rect[1] += tMargin;
        rect[2] -= (lMargin + parent.getRightMargin());
        rect[3] -= (tMargin + parent.getBottomMargin());
        
        if (this.peers && this.peers.length > 0) {
            // for widgets that have peers, take the degree to which all peers currently extend
            // past the masters extents, and reduce the parent space by that amount.  This is
            // required for dropShadows.
            // NOTE: This method is inexact if the peer will respond to setPageRect() on it's
            // master by sticking out yet further, which seems unlikely mid-drag.  
            var peerRect = this.getPeerRect(),
                thisRect = this.getPageRect();
            rect[0] += (thisRect[0] - peerRect[0]);
            rect[1] += (thisRect[1] - peerRect[1]);
            rect[2] -= (peerRect[2] - thisRect[2]);
            rect[3] -= (peerRect[3] - thisRect[3]);
        }
        
        // If the parent has borders, also disallow dragging over the borders.
        var borderSize = parent._calculateBorderSize();
        rect[0] += borderSize.left;
        rect[1] += borderSize.top;
        rect[2] -= borderSize.right + borderSize.left;
        rect[3] -= borderSize.bottom + borderSize.top;
        
        // if the parent has scrollbars
        var scrollBarSize=parent.getScrollbarSize();
        if (parent.vscrollOn) rect[2] -= scrollBarSize;
        if (parent.hscrollOn) rect[3] -= scrollBarSize;

        return rect;
                
    }
	else return [0, 0, isc.Page.getWidth(), isc.Page.getHeight()];
},


setPageRect : function (left, top, width, height, resizeOnly) {

    // if the first argument is an array, normalize it into workable parameters
    // (so that you can say widget.setPageRect(otherWidget.getPageRect()); )
    if (isc.isAn.Array(left)) {
        top = left[1];
        width = left[2];
        height = left[3];
        left = left[0];
    }
	
	// Optionally constrain size and position to the parent's rect during a dragReposition
	// or dragResize interaction.
	// We assume that:
	//  -- setPageRect is called on a keepInParentRect element either to move ~or~ to resize
	//     the element, not both simultaneously.  This is sufficient for user drags, but
    //     wouldn't work if keepInParentRect was intended to block programmatic resize.
	//  -- if either width or height is specified, this is a resize operation
	//  -- resizing occurs from one edge or corner at a time (revisit this if we support
	//     resizing around the center in the future)
	if (this.keepInParentRect && this.ns.EH.dragging && this == this.ns.EH.dragMoveTarget) {
    
		// are we moving or resizing the element?
		var moving = (width == null && height == null);
		
		// set up all of the element & parent coordinate variables
		
        if (width == null) width = this.getVisibleWidth();
        if (height == null) height = this.getVisibleHeight();
	
		var right = left + width,
			bottom = top + height,
			parentRect;

        var explicitRect = isc.isAn.Array(this.keepInParentRect);
		if (explicitRect) {	// use provided rect (e.g. for dragOutline)
			parentRect = this.keepInParentRect;
		} else {	// use parent rect
			parentRect = this.getParentPageRect();
		}

		var parentLeft = parentRect[0],
			parentTop = parentRect[1],
			parentWidth = parentRect[2],
			parentHeight = parentRect[3],
		    parentRight = parentLeft + parentWidth,
			parentBottom = parentTop + parentHeight;
        //this.logWarn("child left/top:"+ [left,top] + 
        //             ", parent left/top:"+ [parentLeft,parentTop]);
        //this.logWarn("child r/b: " + [right,bottom] +
        //             ", parent r/b: " + [parentRight,parentBottom]);
        
        // If the parent already has scrollable content outisde the current viewport in a particular direction, 
        // we should allow the child to be dragged out of the viewport in that direction (not applicable to resize)
        var EH = this.ns.EH,
            dragParent = EH.getDragTarget(EH.getLastEvent()).parentElement;

        // If the widget is keepInParentRect: true but it has no parent, get scrolling info
        // from the Page object
        if (dragParent) {
            var leftScrollExtent = dragParent.getScrollLeft(),
                rightScrollExtent = dragParent.getScrollWidth() - 
                                        dragParent.getViewportWidth() - leftScrollExtent,
                topScrollExtent = dragParent.getScrollTop(),
                bottomScrollExtent = dragParent.getScrollHeight() - 
                                        dragParent.getViewportHeight() - topScrollExtent;
        } else {
            var leftScrollExtent = isc.Page.getScrollLeft(),
                rightScrollExtent = isc.Page.getScrollWidth() - 
                                        isc.Page.getWidth() - leftScrollExtent,
                topScrollExtent = isc.Page.getScrollTop(),
                bottomScrollExtent = isc.Page.getScrollHeight() - 
                                        isc.Page.getHeight() - topScrollExtent;
        }

        if (rightScrollExtent < 0) rightScrollExtent = 0;
        if (bottomScrollExtent < 0) bottomScrollExtent = 0;
                                    
		// test the coordinates and apply constraints
		
		if (moving) {	// moving outside the parent rect?
		
			if (left < parentLeft - leftScrollExtent) {
				left = parentLeft - leftScrollExtent;            
			}
			else if (right > parentRight + rightScrollExtent) {
				left = parentRight + rightScrollExtent - width;
			}
		
			if (top < parentTop - topScrollExtent) {
				top = parentTop - topScrollExtent;
			}
			else if (bottom > parentBottom + bottomScrollExtent) {
				top = parentBottom + bottomScrollExtent - height;
			}
			
		} else {	// resizing outside the parent rect?
		
			if (left < parentLeft) {
				width = width - (parentLeft - left);
				left = parentLeft;
			} else if (right > parentRight) {
				width = width - (right - parentRight);
			}
			
			if (top < parentTop) {
				height = height - (parentTop - top);
				top = parentTop;
			} else if (bottom > parentBottom) {
				height = height - (bottom - parentBottom);
			}

		}
		
	}	
    // end keepInParentRect
	
    
    this.moveBy(left - this.getPageLeft(), top - this.getPageTop());

    if (resizeOnly) {
        
        var oldWidth = this.getVisibleWidth(),
            oldHeight = this.getVisibleHeight(),
            desiredDeltaX = oldWidth - width,
            desiredDeltaY = oldHeight - height;

        this.resizeTo(width,height);
        this.redrawIfDirty("setPageRect"); // to get valid new size

        var actualDeltaX = (oldWidth - this.getVisibleWidth()),
            actualDeltaY = (oldHeight - this.getVisibleHeight());

        if (left > this.getPageLeft()) left -= (desiredDeltaX - actualDeltaX);
        if (top > this.getPageTop()) top -= (desiredDeltaY - actualDeltaY);
    } else {
        this.resizeTo(width,height);
    }

},

// getCanvasLeft() and getCanvasTop()
// returns a canvas's left and top offset relative to their ISC parentElement canvas.

//>	@method	canvas.getCanvasLeft()
//		Return the absolute left coordinate of this object relative to the top/left of it's 
//      SmartClient parent element, in pixels.  Value returned is distance from outside of 
//      this widget's border/margins (if any) to inside of parent's handle.
//		
//		@group	positioning
//
//		@return	(number)	SmartClient canvas left coordinate
//<
getCanvasLeft : function () {

    // See "Widget Positioning and Sizing Methods" comment for a discussion of coordinate systems 
    // in DOM and ISC
    
    // If we haven't been drawn yet, return the specified coordinate
    if (!this.isDrawn()) {
        if (this.position == isc.Canvas.RELATIVE) {
            //>DEBUG technically, an absolutely positioned widget would also have this problem
            // if placed within an element that served as an offsetParent (eg, an absolutely
            // positioned DIV), but that scenario is very unlikely and if we catch it then this
            // warning will fire for the common case of manipulating the coordinates of a
            // top-level absolutely positioned widget before drawing it.
            this.logWarn("getCanvasLeft(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The drawn coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
            //<DEBUG
        }    
        return this.left;
    }
    
    // In Moz, if the widget has been hidden using 'display:none', just return the 
    // specified position
    
    if (isc.Browser.isMoz && this._isDisplayNone()) return this.left;

    // fall through to getLeftOffsetFromElement (passing in the parent canvas if there is one)
    var parent = this.parentElement,
        returnVal = this._getLeftOffsetFromElement(parent == null ? null : parent.getHandle());

    

    return returnVal;
},

//>	@method	canvas.getPageLeft()    ([A])
// Returns the page-relative left coordinate of the widget on the page, in pixels.
//		@visibility external
//		@group	positioning
//		@return	(number)	global left coordinate
//<
getPageLeft : function () {
    if (isc._traceMarkers) arguments.__this = this;
    var handle = this.getClipHandle();

    
    if (handle && isc.Browser.isMoz && this._isDisplayNone()) handle = null;
    
    if (handle == null) {
        // If we haven't been drawn the coordinates may be wrong for a number of reasons - log
        // a warning
        if (!this.isDrawn() && this.position == isc.Canvas.RELATIVE) {
            //>DEBUG technically, an absolutely positioned widget would also have this problem
            // if placed within an element that served as an offsetParent (eg, an absolutely
            // positioned DIV), but that scenario is very unlikely and if we catch it then this
            // warning will fire for the common case of manipulating the coordinates of a
            // top-level absolutely positioned widget before drawing it.
            this.logWarn("getPageLeft(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The page level coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
            //<DEBUG
        }

        var parent = this.parentElement;
        if (parent) {
            var scrollDelta = 0;
            if (parent.hscrollOn) {
                if (!this.isRTL()) scrollDelta = parent.getScrollLeft();
                else {
                    var maxScroll = parent.getScrollWidth() - parent.getViewportWidth();
                    scrollDelta = -1 * (maxScroll - parent.getScrollLeft());
                }
            }
            return this.getOffsetLeft() + parent.getLeftBorderSize() + parent.getLeftMargin() + 
                    parent.getPageLeft() - scrollDelta;
                    
        } else {
            return this.getOffsetLeft();
        }
    
    }

    
    if (isc.Browser.isMoz) {
        if (this.useClientRectAPI && isc.Browser.geckoVersion > 20071109 && 
            handle.getBoundingClientRect != null) 
        {
            var left = handle.getBoundingClientRect().left;
            // boundingClientRect returns position inside margins, and coords are relative to
            // viewport rather than page
            left -= this.getLeftMargin();
            left += isc.Page.getScrollLeft();
            return left;
        } else if ((this.useBoxObjectAPI || 
                    (this.useBoxObjectAPISelectively && isc._useBoxShortcut)) &&
                   isc.Browser.geckoVersion > 20061010 && 
                   !isc.Browser.isCamino && document.getBoxObjectFor != null) 
        {
            
            var box = this.getDocument().getBoxObjectFor(handle);
            if (box) {
                var left = box.x - this.getLeftMargin(),
                    parent = this.parentElement;
                while (parent) {
                    left += parent.getScrollLeft();
                    parent = parent.parentElement;
                }
            }
        }
    }

    // If we are drawn use _getLeftOffsetFromElement().
    
    return this._getLeftOffsetFromElement()
},


useClientRectAPI:true,
useBoxObjectAPI:false,
useBoxObjectAPISelectively:true,

    
// _getLeftOffsetFromElement(targetElement)
//
// DOM Only method to return our absolute position within a DOM parent element
// If no target parent element is passed, we return page level position.
//
_getLeftOffsetFromElement : function (targetElement) {

    // if we're not passed an element, determine the offset from the top level HTML element.
    if (targetElement == null) targetElement = this.getDocumentBody();
    
    var left = this.ns.Element._getLeftOffsetFromElement(this.getClipHandle(), targetElement, this.isRTL());

    
    
    return left;
},


//>	@method	canvas.getCanvasTop()
//		Return the absolute top coordinate of this object relative to it's SmartClient parent
//      element (viewport top), in pixels.  Value returned is distance from outside of this
//      widget's border/margins (if any) to inside of parent's handle (top of content, not page
//      coordinate of widget, so does not change when the widget is scrolled).
//		
//		@group	positioning
//
//		@return	(number)	SmartClient canvas top coordinate
//<
getCanvasTop : function () {

    // See "Widget Positioning and Sizing Methods" comment for a discussion of coordinate systems 
    // in DOM and ISC

    if (!this.isDrawn()) {
        //>DEBUG
        if (this.position == isc.Canvas.RELATIVE) {
            this.logWarn("getCanvasTop(): Called on undrawn relatively-position widget '" +
                         this.getID() + "'.  The drawn coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
        } //<DEBUG

        return this.top;
    }        
    
    // In Moz, if the widget has been hidden using 'display:none', just return the 
    // specified position
    
    if (isc.Browser.isMoz && this._isDisplayNone()) return this.top;
    
    // fall through to getTopOffsetFromElement (passing in the parent canvas if there is one)
    var returnVal = this._getTopOffsetFromElement(this.parentElement == null ? 
                                                  null : this.parentElement.getHandle());

    

    return returnVal;        
},

//>	@method	canvas.getPageTop() ([A])
// Returns the page-relative top coordinate of the widget on the page, in pixels 
//      @visibility external
//		@group	positioning
//		@return	(number)	GLOBAL top coordinate
//<
getPageTop : function () {

    var handle = this.getClipHandle();
    
    
    if (handle && isc.Browser.isMoz && this._isDisplayNone()) handle = null;

    if (handle == null) {
        // Log a warning if we haven't been drawn yet
        if (!this.isDrawn() && this.position == isc.Canvas.RELATIVE) {
            //>DEBUG technically, an absolutely positioned widget would also have this problem
            // if placed within an element that served as an offsetParent (eg, an absolutely
            // positioned DIV), but that scenario is very unlikely and if we catch it then this
            // warning will fire for the common case of manipulating the coordinates of a
            // top-level absolutely positioned widget before drawing it.
            this.logWarn("getPageTop(): Called on undrawn relatively-positioned widget '" +
                         this.getID() + "'.  The page level coordinates can not be reliably " +
                         "calculated until the widget has drawn - returning estimated position");
            //<DEBUG
        }
    
        var parent = this.parentElement;
        if (parent) {
            // parent.getPageTop gives us page coords from outside border/margin of 
            // parent - offsetLeft/offsetTop gives us the value to the inside of the parent,
            // so we need to add the parent's border/margin
            return this.getOffsetTop() + parent.getTopBorderSize() + parent.getTopMargin() + 
                    parent.getPageTop() - parent.getScrollTop();
        } else {
            return this.getOffsetTop();
        }
    }

    
    if (isc.Browser.isMoz) {
        if (this.useClientRectAPI && isc.Browser.geckoVersion > 20071109 && 
            handle.getBoundingClientRect != null) 
        {
            var top = handle.getBoundingClientRect().top;
            // boundingClientRect returns position inside margins, and coords are relative to
            // viewport rather than page
            top -= this.getTopMargin();
            top += isc.Page.getScrollTop();
            return top;
            
        } else if ((this.useBoxObjectAPI || 
                    (this.useBoxObjectAPISelectively && isc._useBoxShortcut)) &&
                   isc.Browser.geckoVersion > 20061010 && 
                   !isc.Browser.isCamino && document.getBoxObjectFor != null) 
        {
            //this.logWarn("using shortcut for top");
            var box = this.getDocument().getBoxObjectFor(handle);
            if (box) {
                var top = box.y - this.getTopMargin(),
                    parent = this.parentElement;
                while (parent) {
                    top += parent.getScrollTop();
                    parent = parent.parentElement;
                }
            }
        }
    }

    return this._getTopOffsetFromElement();
    
},

// Return our absolute position within a DOM parent element.
// If no target parent element is passed, we return page level position.
_getTopOffsetFromElement : function (targetElement) {
    // if we're not passed an element, determine the offset from the top level HTML element.
    if (targetElement == null) targetElement = this.getDocumentBody();

    var top = this.ns.Element._getTopOffsetFromElement(this.getClipHandle(), targetElement);

    
    
    return top;

},
                    
//>	@method	canvas.getPageRight()
// Return the page-relative right coordinate of this object, in pixels.
//		
//		@group	positioning
//
//		@return	(number)	GLOBAL right coordinate
// @visibility external
//<
getPageRight : function (){
	return this.getPageLeft() + this.getVisibleWidth();
},


//>	@method	canvas.getPageBottom()
// Return the page-relative bottom coordinate of this object, in pixels.
//		@group	positioning
//
//		@return	(number)	GLOBAL bottom coordinate
// @visibility external
//<
getPageBottom : function (){
	return this.getPageTop() + this.getVisibleHeight();
},


getPageRect : function () {
    return [this.getPageLeft(), this.getPageTop(), 
            this.getVisibleWidth(), this.getVisibleHeight()];
},

// Scrolling Mechanisms
// --------------------------------------------------------------------------------------------

//>	@method	canvas.usingCSSScrollbars()	(A)
// Return whether or not we are configured to show native CSS scrollbars when
// scrollWidth/Height exceeds viewport width/height.
//		@group	scrolling
//
//		@return	(boolean)	
//<
usingCSSScrollbars : function () {
	return  ! this.showCustomScrollbars && 
        (this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL);
},

//>	@method	canvas.getScrollingMechanism()	(A)
//			Return how we're implementing scrolling - one of 3 possibilities:
//           - "native" = assigning directly to handle.scrollLeft / scrollTop
//           - "clip" = using a clip region to simulate scrolling
//           - "nestedDiv" = moving an inner div within an outer clipDiv
//		@group	scrolling
//
//		@return	(enum)  one of "native", "clip", "nestedDiv"
//<
// Note: If we are showing css scrollbars (this.showNativeScrollbars is true, and this.overflow
// is auto or scroll), scrollingMechanism is always native.
// Otherwise it varies by platform (due to limitations in the various platforms)
getScrollingMechanism : function () {

    
    if (!this._scrollingMechanism) {
        
        if (!this.showCustomScrollbars && 
            (this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL)) 
        {
            this._scrollingMechanism = isc.Canvas.NATIVE;
        } else {
            // We're either showing custom scrollbars or not showing scrollbars at all for this
            // widget
        
            
            if ((isc.Browser.isSafari && isc.Browser.SafariVersion < 125) || 
                       (isc.Browser.isMoz && isc.Browser.isUnix && isc.Browser.geckoVersion <= 20031007))
            {
                this._scrollingMechanism = isc.Canvas.NESTED_DIV;
                
            // In every other case we can assign directly to handle.scrollLeft / scrollTop                
            } else {
                this._scrollingMechanism = isc.Canvas.NATIVE;
            }
        }
    }
    
    return this._scrollingMechanism;

},


// Border, Padding and Margin
// --------------------------------------------------------------------------------------------
//  Border, Margin and Padding properties can all be specified for widgets at the widget level, or
//  via the css class applied to them (through their className property)
//  We provide methods to get at the thickness of these properties for each widget - whether the 
//  property is defined on the widget directly, or through it's css class.



//>	@method canvas.setMargin()
// Set the CSS Margin, in pixels, for this component.  Margin provides blank space outside of
// the border.
// <P>
// This property sets the same thickness of margin on every side.  Differing per-side
// margins can be set in a CSS style and applied via +link{styleName}.
// <P>
// Note that the specified size of the widget will be the size <b>including</b> the margin
// thickness on each side.
// 
// @param margin (number) new margin in pixels
//
// @visibility external
//<
setMargin : function (margin) {
    
    this._cachedMargins = null;
    this._fullMargins = null;
    
    if (margin == null) { 
        delete this.margin
    } else {
        var origMargin = margin;
        if (isc.isA.String(margin)) margin = parseInt(margin);
        if (!isc.isA.Number(margin)) {
            this.logWarn("setMargin() passed invalid margin:"+ origMargin + ", ignoring.");
            return;
        }
        this.margin = margin;
    }
    var styleHandle = this.getStyleHandle();
    if (!styleHandle) return;
    
    if (margin == null) {
        // IE JS errors with an 'Invalid Argument' error if you try to set the margin to null or
        // undefined.  Zero is the default according to the specs and it works
        styleHandle.margin = 0;
    } else {
        styleHandle.margin = this.margin + isc.px;
    }
    // adjustOverflow - since this will change our handle-size
    
    this.adjustOverflow("setMargin");
},

//>	@method canvas.getMargin()
//			Returns the explicitly specified margin for this widget (set up via this.setMargin())
//		@group	appearance
//		@return	(string)    margin property for this widget
//<
getMargin : function () {
    return this.margin;
},


//>	@method canvas.getTopMargin()
//			Return the size of the top margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no top margin
//<
getTopMargin : function () {
    return this._calculateMargins().top;
},


//>	@method canvas.getLeftMargin()
//			Return the size of the left margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no left margin
//<
getLeftMargin : function () {
    return this._calculateMargins().left;
},

//>	@method canvas.getBottomMargin()
//			Return the size of the bottom margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no bottom margin
//<
getBottomMargin : function () {
    return this._calculateMargins().bottom;
},


//>	@method canvas.getRightMargin()
//			Return the size of the right margin for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no right margin
//<
getRightMargin : function () {
    return this._calculateMargins().right;
},

//>	@method canvas._calculateMargins()
//      Determines the size of the margins for this widget (on each side), by looking at the
//      widget's "Margin" property, it's handle, and it's CSS class.
//      Uses cacheing for speed
//      
//		@group	appearance
//      @return (object)    Object with properties 'left', 'top', 'bottom', 'right', specifying the
//                          width in pixels of the margin on each side of this widget.
//<

_calculateMargins : function () {
    
    var attachedPeers = this._attachedPeerMap,
        hasAPs = (attachedPeers != null),
        topPeers,leftPeers,rightPeers,bottomPeers;

    if (hasAPs) {
        topPeers = attachedPeers.top;
        bottomPeers = attachedPeers.bottom;
        leftPeers = attachedPeers.left;
        rightPeers = attachedPeers.right;                        
        if ((!topPeers || topPeers.length == 0) && 
            (!bottomPeers || bottomPeers.length == 0) && 
            (!leftPeers || leftPeers.length == 0) && 
            (!rightPeers || rightPeers.length == 0)) hasAPs = false;   
    }
    if (!this._edgesAsPeer() && !hasAPs) return this._calculateNormalMargins();
    
    
    var fullMargins = this._fullMargins;
    if (fullMargins) return fullMargins;

    var margins = this._getSpecifiedMargins();
    fullMargins = {
        left:margins.left,
        right:margins.right,
        top:margins.top,
        bottom:margins.bottom
    };
    
    if (hasAPs) {
        
        if (topPeers) {
            for (var i = 0; i < topPeers.length; i++) {
                var topPeer = topPeers[i];
                fullMargins.top += topPeer.getVisibleHeight(); 
                if (topPeer._attachedPeerOffset != null) {
                    fullMargins.top -= topPeer._attachedPeerOffset;
                }
            }
        }
        if (bottomPeers) {
            for (var i = 0; i < bottomPeers.length; i++) {
                var bottomPeer = bottomPeers[i];
                fullMargins.bottom += bottomPeer.getVisibleHeight();
                if (bottomPeer._attachedPeerOffset != null) {
                    fullMargins.bottom -= bottomPeer._attachedPeerOffset;
                }
            }
        }
        if (leftPeers) {
            for (var i = 0; i < leftPeers.length; i++) {
                var leftPeer = leftPeers[i];
                fullMargins.left += leftPeer.getVisibleWidth();
                if (leftPeer._attachedPeerOffset != null) {
                    fullMargins.left -= leftPeer._attachedPeerOffset;
                }
            }
        }
        if (rightPeers) {
            for (var i = 0; i < rightPeers.length; i++) {
                var rightPeer = rightPeers[i];
                fullMargins.right += rightPeer.getVisibleWidth();
                if (rightPeer._attachedPeerOffset != null) {
                    fullMargins.right -= rightPeer._attachedPeerOffset;
                }
            }
        }
    }

    //>RoundCorners add to margins to leave room for surrounding EdgedCanvas
    
    if (this._edgesAsPeer()) {
        var edge = this._createEdges();
        // add to margins to allow room for the edgedCanvas
        fullMargins.left += edge._leftMargin,
        fullMargins.right += edge._rightMargin,
        fullMargins.top += edge._topMargin,
        fullMargins.bottom += edge._bottomMargin
    }
    //<RoundCorners
    
    
    return (this._fullMargins = fullMargins);
},

_getSpecifiedMargins : function () {
    var drawn = this._drawn;
    this._drawn = false;
    var margins = this._calculateNormalMargins();
    this._drawn = drawn;
    return margins;
},

_calculateNormalMargins : function () {

    // If we've already calculated it, return the cached version for speed
    // (Cleared out by 'setMargin()')
    if (this._cachedMargins != null) return this._cachedMargins;

    // First check for this.margin / directly applying the margin to the DOM.
    // We'll then check the css class for this widget for any margins we don't find applied directly

    // There are various options for setting the css margin width - 
    //  Measure:    float followed by units designator (cm, mm, in, pt, pc, px, OR em, or ex) 
    //  Percentage: (fairly self explanatory!)
    //
    // We currently only handle returning widths specified in pixels.
    var margins = {},
        pxString = isc.px;
    
    // If it's not drawn - Look at this.margin
    if (!this.isDrawn()) {

        // We are assuming here that the margin will be uniform on all sides - something like
        // "1px"
        var marginString = this.margin;

        if (isc.isA.String(marginString)) {
            // We should handle either "2" or "2px" format margin property
            // (This will also handle "2px 2px 2px 2px", but not asymmetric margins applied in this
            //  way)
            if (marginString.endsWith(pxString) || parseInt(marginString) + "" == marginString) 
                marginString = parseInt(marginString);
        }
        
        // This will handle the case where a margin was specified as a number directly, or where
        // we've parsed a string
        if (isc.isA.Number(marginString)) {
            margins.top = marginString;
            margins.bottom = marginString;
            margins.left = marginString;
            margins.right = marginString;
            
            // cache and return it, we're done
            this._cachedMargins = margins;
            return margins;
        }
        
    // If it is drawn, check the DOM for the margin actually applied to the div         
    } else {
        
        
        var handleStyle = this.getStyleHandle(),
            marginLeft = handleStyle.marginLeft,
            marginRight = handleStyle.marginRight,
            marginTop = handleStyle.marginTop,
            marginBottom = handleStyle.marginBottom;
        
        if (isc.isA.String(marginLeft) && marginLeft.endsWith(pxString))
            marginLeft = parseInt(marginLeft);
        
        if (isc.isA.String(marginRight) && marginRight.endsWith(pxString))
            marginRight = parseInt(marginRight)
            
        if (isc.isA.String(marginTop) && marginTop.endsWith(pxString))
            marginTop = parseInt(marginTop);
        
        if (isc.isA.String(marginBottom) && marginBottom.endsWith(pxString))
            marginBottom = parseInt(marginBottom)
            
        if (isc.isA.Number(marginLeft)) margins.left = marginLeft;
        if (isc.isA.Number(marginRight)) margins.right = marginRight;
        if (isc.isA.Number(marginTop)) margins.top = marginTop;
        if (isc.isA.Number(marginBottom)) margins.bottom = marginBottom;        
    }
    
    // Having looked at the handle (or 'margin' property for undrawn widgets), if we have not 
    // determined margin sizes for any side, the widget will display any margin specified on the
    // css class applied to it.
    // Check the styleObject from the className for any margin's we haven't already determined.
    if (this.className) {

        if (!isc.isA.Number(margins.left)) 
            margins.left = isc.Element._getLeftMargin(this.className);
        if (!isc.isA.Number(margins.right)) 
            margins.right = isc.Element._getRightMargin(this.className);
        if (!isc.isA.Number(margins.top)) 
            margins.top = isc.Element._getTopMargin(this.className);
        if (!isc.isA.Number(margins.bottom)) 
            margins.bottom = isc.Element._getBottomMargin(this.className);
    } else {
        // widget has no margin on any sides we haven't got yet!
        if (!isc.isA.Number(margins.left)) 
            margins.left = 0;
        if (!isc.isA.Number(margins.right)) 
            margins.right = 0;
        if (!isc.isA.Number(margins.top)) 
            margins.top = 0;
        if (!isc.isA.Number(margins.bottom)) 
            margins.bottom = 0;
    }
    
    
    return (this._cachedMargins = margins);
},


//>	@method canvas.getTopBorderSize()
//			Return the size of the top border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getTopBorderSize : function () {
    return this._calculateBorderSize().top;
},

//>	@method canvas.getBottomBorderSize()
//			Return the size of the bottom border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getBottomBorderSize : function () {
    return this._calculateBorderSize().bottom;
},

//>	@method canvas.getLeftBorderSize()
//			Return the size of the left border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getLeftBorderSize : function () {
    return this._calculateBorderSize().left;
},

//>	@method canvas.getRightBorderSize()
//			Return the size of the right border for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getRightBorderSize : function () {
    return this._calculateBorderSize().right;
},


//>	@method canvas.getHBorderSize()
//			Return the size of the horizontal borders (left and right) for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getHBorderSize : function () {
    return (this.getLeftBorderSize() + this.getRightBorderSize());
},


//>	@method canvas.getVBorderSize()
//			Return the total size of the vertical borders (top and bottom) for this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no border
//<
getVBorderSize : function () {
    return this.getTopBorderSize() + this.getBottomBorderSize();
},

//>	@method canvas._calculateBorderSize()
//      Determines the size of the border for this widget (on each side), from the 'border' property
//      for the widget, and any specified css class.
//      Stores resulting values in _cachedBorderSize() property.
//      
//		@group	appearance
//      @return (object)    Object with properties 'left', 'top', 'bottom', 'right', specifying the
//                          width in pixels of the border on each side of this widget.
//<
_calculateBorderSize : function () {
    
    // If we've already calculated it, return the cached version for speed
    // (Cleared out by 'setBorder()')
    if (this._cachedBorderSize != null) return this._cachedBorderSize;

    // Determine the borderSize from the DOM.
    var borderSizes = {},
        pxString = isc.px;
    
    // The Border for a widget can be applied directly to its handle's style attribute - done
    // via the "border" property of the widget, or (if that is not defined), it is picked up
    // from the CSS class for the widget.
    // In this method we will check for an explictly specified border for the widget, and if none
    // is found, fall through to checking the border on the widget's css class.
    // - Note on the 'border' property.
    //   widget.border is applied directly to the clipHandle's style. It should be of the form
    //   '2px solid black' (so a string of CSS designating a border style).
    //   We don't support the developer applying different borders to different sides, except via
    //   a css class applied to the widget.
    
    
    // There are various options for setting the css border width - 
    //  String:     medium, thin, thick
    //  Measure:    float followed by units designator (cm, mm, in, pt, pc, px, OR em, or ex) 
    //
    // We only support sizes specified in px.
    

    // Border applied to the handle directly
    //
    // If it's not drawn - Look at border specified via this.border
    if (!this.isDrawn()) {

        // We are assuming here that the border will be uniform on all sides - something like
        // "1px solid black"
        var borderString = this.border;
        
        // If we can't find a width in pixels, assume width is not defined in this.border
        // We check for this via the presence of the 'px' string. Note that 
        // css supports specifying the border style in any order, so we need to use a regexp to
        // find the right part of the string (next to the 'px').
        
        if (borderString != null && isc.contains(borderString, pxString)) {
            var borderSize = borderString.match(/\s*\d+px/g);

            // All the borders should be the same size - use the first size encountered
            if (isc.isAn.Array(borderSize)) borderSize = parseInt(borderSize[0]);
            else borderSize = parseInt(borderSize);
            
            if (isc.isA.Number(borderSize)) {
                this._cachedBorderSize = {
                    left:borderSize,
                    right:borderSize,
                    top:borderSize,
                    bottom:borderSize
                }
                return this._cachedBorderSize;
            }
        }
        
    // If it is drawn, check the DOM for the border actually applied to the div         
    } else {

        // examine the style of the drawn HTML element (before looking at the css class)
        
        
        var handleStyle = this.getStyleHandle(),
            borderLeft = handleStyle.borderLeftWidth,
            borderRight = handleStyle.borderRightWidth,
            borderTop = handleStyle.borderTopWidth,
            borderBottom = handleStyle.borderBottomWidth;
        
        if (isc.isA.String(borderLeft) && borderLeft.endsWith(pxString))
            borderLeft = parseInt(borderLeft);
        
        if (isc.isA.String(borderRight) && borderRight.endsWith(pxString))
            borderRight = parseInt(borderRight)
            
        if (isc.isA.String(borderTop) && borderTop.endsWith(pxString))
            borderTop = parseInt(borderTop);
        
        if (isc.isA.String(borderBottom) && borderBottom.endsWith(pxString))
            borderBottom = parseInt(borderBottom)
        
        if (isc.isA.Number(borderLeft)) borderSizes.left = borderLeft;
        if (isc.isA.Number(borderRight)) borderSizes.right = borderRight;
        if (isc.isA.Number(borderTop)) borderSizes.top = borderTop;
        if (isc.isA.Number(borderBottom)) borderSizes.bottom = borderBottom;        
        
    }
    
    // Having looked at the handle (or 'border' property for undrawn widgets), if we have not 
    // determined sizes for any side, derive the border sizes from the css class applied to the
    // widget.
    if (this.className) {
        // Determine the borderWidth from the css style class for this element
        if (!isc.isA.Number(borderSizes.left)) 
            borderSizes.left = isc.Element._getLeftBorderSize(this.className);
        if (!isc.isA.Number(borderSizes.right)) 
            borderSizes.right = isc.Element._getRightBorderSize(this.className);
        if (!isc.isA.Number(borderSizes.top)) 
            borderSizes.top = isc.Element._getTopBorderSize(this.className);
        if (!isc.isA.Number(borderSizes.bottom)) 
            borderSizes.bottom = isc.Element._getBottomBorderSize(this.className);
    } else {
        // widget has no border on any sides we haven't got yet!
        if (!isc.isA.Number(borderSizes.left)) 
            borderSizes.left = 0;
        if (!isc.isA.Number(borderSizes.right)) 
            borderSizes.right = 0;
        if (!isc.isA.Number(borderSizes.top)) 
            borderSizes.top = 0;
        if (!isc.isA.Number(borderSizes.bottom)) 
            borderSizes.bottom = 0;
    }
    
    
    return (this._cachedBorderSize = borderSizes);
},

// Unexposed method to set explicit per-side padding
setTopPadding : function (padding) {
    this.topPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) this.getHandle().paddingTop = padding;
},
setLeftPadding : function (padding) {
    this.leftPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) this.getHandle().paddingLeft = padding;
},
setRightPadding : function (padding) {
    this.rightPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) this.getHandle().paddingRight = padding;
},
setBottomPadding : function (padding) {
    this.bottomPadding = padding;
    if (isc.isA.Number(padding)) padding += "px";
    if (this.isDrawn()) this.getHandle().paddingBottom = padding;
},

//>	@method canvas.setPadding()
// Set the CSS padding of this component, in pixels.  Padding provides space between the border
// and the component's contents.
// <P>
// This property sets the same thickness of padding on every side.  Differing per-side
// padding can be set in a CSS style and applied via +link{styleName}.
// <P>
// @group appearance
// @param newPadding (number) new padding in pixels
// @visibility external
//<
_$0px:"0px",
setPadding : function (padding) {
    this._cachedPadding = null;
    
    if (padding != null) {
        var origPadding = padding;
        if (isc.isA.String(padding)) padding = parseInt(padding);
        if (!isc.isA.Number(padding)) {
            this.logWarn("setPadding passed unrecognized value:"+ origPadding + " - ignoring");
            return;
        }
    }
    this.padding = padding;
    
    // No support in non DOM browsers really
    var handle = isc.Browser.isDOM ? this.getHandle() : null;
    if (!handle) {
        return;
    }

    // if padding is null - clear out this.padding
    if (padding == null) {
    
        // clear out the padding from the handle 
        // if we're using clipDivs, also clear out any padding from the clipDiv, since we'll 
        // want the css class's padding (if there is any) to be applied.        
        handle.style.padding = null;
        if (this.useClipDiv) this.getClipHandle().style.padding = null;
        
    } else {
        // update the handle
        // Note - if we're using clip divs, ensure that the clip div's padding is explicitly
        // set to zero so we don't get nested padding from the specified padding property and
        // the className applied to the element
        handle.style.padding = this.padding + isc.px;
        if (this.useClipDiv) this.getClipHandle().style.padding = this._$0px;
    }
    
},

//>	@method canvas.getPadding()
//			Return the size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getPadding : function () {
    return this.padding;
},

//>	@method canvas.getTopPadding()
//			Return the size of the top padding above this canvas' content.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getTopPadding : function () {
    return this._calculatePadding().top;
},

//>	@method canvas.getBottomPadding()
//			Return the size of the bottom padding (below this canvas' content).
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getBottomPadding : function () {
    return this._calculatePadding().bottom;
},

//>	@method canvas.getLeftPadding()
//			Return the size of the left padding for this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getLeftPadding : function () {
    return this._calculatePadding().left;
},

//>	@method canvas.getRightPadding()
//			Return the size of the right padding for this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getRightPadding : function () {
    return this._calculatePadding().right;
},

//>	@method canvas.getVPadding()
//			Return the vertical size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getVPadding : function () {
    return this.getTopPadding() + this.getBottomPadding();
},


//>	@method canvas.getHPadding()
//			Return the horizontal size of the padding around this canvas
//		@group	appearance
//		@return	(number)	size in pixels, 0 == no padding
//<
getHPadding : function () {
    return this.getLeftPadding() + this.getRightPadding();
},

//>	@method canvas._calculatePadding()
//			Determine the size of the padding on each side of this canvas.
//          Derives value from explicitly specified 'padding' if present, or from css class.
//          Stores in this._cachedPadding object.
//		@group	appearance
//<
_calculatePadding : function () {

    // If we've already calculated padding for this widget, return the cached version for speed
    // (cleared out by 'setPadding()')    
    if (this._cachedPadding != null) return this._cachedPadding;

    // Determine the padding size from the DOM.
    
    var padding = {},
        pxString = isc.px;
    
    // if it's drawn examine the style of the drawn HTML element first
    if (this.isDrawn() && this.getHandle() != null) {
        // Note: if we're writing out two DIVS (as with Moz), the padding will be
        // applied to the inner contentDiv, rather than the outer clipDiv
        // We must use getHandle().style rather than getStyleHandle() therefore as
        // getStyleHandle() will examine the style applied to the clipDiv.
        
        var handleStyle = this.getHandle().style;

        if (handleStyle.paddingTop != null && !isc.isAn.emptyString(handleStyle.paddingTop) &&
            handleStyle.paddingTop.endsWith(pxString)) {
                padding.top = parseInt(handleStyle.paddingTop);
        }
        if (handleStyle.paddingBottom != null && !isc.isAn.emptyString(handleStyle.paddingBottom) &&
            handleStyle.paddingBottom.endsWith(pxString)) {
                padding.bottom = parseInt(handleStyle.paddingBottom);
        }
        
        if (handleStyle.paddingLeft != null && !isc.isAn.emptyString(handleStyle.paddingLeft) &&
            handleStyle.paddingLeft.endsWith(pxString)) {
                padding.left = parseInt(handleStyle.paddingLeft);
        }

        if (handleStyle.paddingRight != null && !isc.isAn.emptyString(handleStyle.paddingRight) &&
            handleStyle.paddingRight.endsWith(pxString)) {
                padding.right = parseInt(handleStyle.paddingRight);
        }                        

        // If the padding is not set here, this will continue to check the CSS class style
        
    // if the widget is not drawn, but this.padding was specified, that takes precidence over the
    // css class applied to the widget.
    } else if (this.padding) {  
        padding.left = padding.right = padding.bottom = padding.top = parseInt(this.padding);

    }

    // Having looked at the handle (or 'padding' property for undrawn widgets), if we have not 
    // determined sizes for any side, derive the padding sizes from the css class applied to the
    // widget.
    if (this.className) {
        if (!isc.isA.Number(padding.left)) padding.left = isc.Element._getLeftPadding(this.className);
        if (!isc.isA.Number(padding.right)) padding.right = isc.Element._getRightPadding(this.className);        
        if (!isc.isA.Number(padding.top)) padding.top = isc.Element._getTopPadding(this.className);
        if (!isc.isA.Number(padding.bottom)) padding.bottom = isc.Element._getBottomPadding(this.className);        
	} else {
        // Padding not explicitly set == padding is zero
        if (!isc.isA.Number(padding.left)) padding.left = 0;
        if (!isc.isA.Number(padding.right)) padding.right = 0;
        if (!isc.isA.Number(padding.top)) padding.top = 0;
        if (!isc.isA.Number(padding.bottom)) padding.bottom = 0;
    }
    
    
    return (this._cachedPadding = padding);
},


// Containment and Intersection
// --------------------------------------------------------------------------------------------


//>	@method	canvas.containsPoint()  ([A])
// Return whether or not this object contains the specified global (x,y) coordinates.
// <P>
// Will return false if any parentElement does not contain the specified point, (EG:
// you're hovering over an element's absolute location, but it is scrolled out of 
// view in a parent element)
//
//      @visibility external
//		@group	positioning
//
//		@param	x		(number)	GLOBAL x-coordinate
//		@param	y		(number)	GLOBAL y-coordinate
//		@param	[withinViewport]	(boolean)	point lies specificly within our viewport
//                                              (drawn area excluding margins and scrollbars if
//                                              present)
//
//		@return	(boolean)	true if this object contains the specified point; false otherwise
//<
containsPoint : function (x, y, withinViewport) {
    if (isc._traceMarkers) arguments.__this = this;
    // always bail if we're not visible
    if (!this.isVisible() || !this.isDrawn()) return false;

    //this.logWarn("containsPoint called");

    if (withinViewport == null) withinViewport = false;
    
    // as a quick initial check, see if the point is within the page rect at all
    
    // Note: don't return true if the specified point is over our margin.
    var myPageLeft = this.getPageLeft() + this.getLeftMargin();
    if (x < myPageLeft) return false;
    var myPageTop = this.getPageTop() + this.getTopMargin();

    if (y < myPageTop) return false;
    var myWidth = withinViewport ? this.getViewportWidth() 
                                 : (this.getVisibleWidth() - this.getHMarginSize());
    if (x > myPageLeft + myWidth) return false;
    var myHeight = withinViewport ? this.getViewportHeight() 
                                  : (this.getVisibleHeight() - this.getVMarginSize());

    if (y > myPageTop + myHeight) return false;
    var pageLeft = 0, pageTop = 0;

    // Iterate through any parent elements, verifying that the point is contained in their
    // viewports 
    
    // Use 'getCanvasLeft()' / 'getCanvasTop()' to determine the page level position of
    // each parentElement, by summing these values from the top level element.

    // create an array consisting of this widget and all parents
    var parentChain = this._parentChain = this._parentChain || []; // reuse an array
    parentChain.length = 1;
    parentChain[0] = this;

    var i = 1, currentParent = this;
    while (currentParent.parentElement != null) {
        currentParent = currentParent.parentElement
        parentChain[i] = currentParent;
        i++;
    }

    var viewportWidth, viewportHeight;
        
    // iterate backwards through the array, from top level parent down to us.
    // If we hit a case where we're not in the viewport, quit. 
    for (var j = parentChain.length - 1; j >= 0 ; j--) {

        var widget = parentChain[j];

        pageLeft += widget.getCanvasLeft();
        pageTop += widget.getCanvasTop();
        
        
        if (j + 1 < parentChain.length) {
            var lastParent = parentChain[j+1];
            pageLeft -= lastParent.getScrollLeft();
            pageTop -= lastParent.getScrollTop();
        }
        
        // Always adjust for margins
        pageLeft += widget.getLeftMargin();
        pageTop += widget.getTopMargin();

        if (widget == this && !withinViewport) {
            // respect the withinViewport flag 
            viewportWidth = widget.getVisibleWidth() - widget.getHMarginSize();
            viewportHeight = widget.getVisibleHeight() - widget.getVMarginSize();
        } else {
            
            
            
            pageLeft += widget.getLeftBorderSize();
            pageTop += widget.getTopBorderSize();
            
            viewportWidth = widget.getViewportWidth();
            viewportHeight = widget.getViewportHeight();
        }

        //this.logWarn("checking widget: " + widget +
        //             " with calculated rect: " + [pageLeft, pageTop, 
        //                                          viewportWidth, viewportHeight] +
        //             " page rect: " + widget.getPageRect());

      	if  ( !((x >= pageLeft) && (x <= pageLeft + viewportWidth) && 
               (y >= pageTop) && (y <= pageTop + viewportHeight))       )   
        {
            //this.logWarn("not within widget: " + widget);
            return false;
        }
    }
    return true;
},

//>	@method	canvas.visibleAtPoint()  ([A])
// Does this widget contain the specified global (x,y) coordinates, and have no other widgets
// also at the specified position, obscuring this one?  This is commonly used for (for example)
// drag and drop interactions.
//
//      @visibility external
//		@group	positioning
//
//		@param	x		(number)	GLOBAL x-coordinate
//		@param	y		(number)	GLOBAL y-coordinate
//		@param	[withinViewport]	(boolean)	point lies within our viewport rather than
//                                              just our drawn area
//      @param  [ignoreWidgets]  (canvas)    If passed ignore widget(s), do not check whether 
//                                          those widgets occludes this one.
//
//		@return	(boolean)	true if this object contains the specified point; false otherwise
//<

visibleAtPoint : function (x, y, withinViewport, ignoreWidgets) {
    if (isc._traceMarkers) arguments.__this = this;

    

    
    if (!this.containsPoint(x,y,withinViewport)) {
        return false;
    }
 
    if (!isc.isAn.Array(ignoreWidgets)) ignoreWidgets = [ignoreWidgets];
    
    // To determine whether there are any widgets obscuring this widget from the specified
    // point we need to check whether our siblings and the siblings of each of our parents i
    // are positioned over the point and have a higher z-index than this widget.
    var currentWidget = this;
    
    while (currentWidget != null) {
        var siblings = (currentWidget.parentElement != null ? 
                        currentWidget.parentElement.children : 
                        isc.Canvas._topCanvii);
        for (var i = 0; i < siblings.length; i++) {
            var sibling = siblings[i];
            // avoid checking this widget, any ignoreWidgets, and any widgets tagged
            // with isMouseTransparent:true
            if (sibling == null || sibling == currentWidget ||
                !sibling.isDrawn() || !sibling.isVisible() ||
                ignoreWidgets.contains(sibling) ||
                sibling.isMouseTransparent ||
                (sibling.getZIndex() < currentWidget.getZIndex())) 
            {
                continue;
            }

            // can't be occluded by event mask
            if (sibling._maskTarget) continue;

            // You can't be occluded by a sibling's scrollbar/thumb without also being occluded
            // by the master of that scrollbar.
            // You could be occluded by your parent's scrollbar, iff the parent was both H&V
            // scrolling, but drag scrolling should bring you into view immediately, so we
            // ignore this case.
            if (isc.isA.Scrollbar(sibling) || isc.isA.ScrollThumb(sibling)) continue;

            // ignore edges generated by showEdges:true.  Note DropShadow is always
            // mouseTransparent
            if (isc.EdgedCanvas && isc.isA.EdgedCanvas(sibling) &&
                sibling.masterElement && 
                sibling.masterElement._edgedCanvas == sibling) continue; 

            // Layouts never allow members to occlude each other, so skip the sibling if both
            // the current parent under consideration and it's sibling are members of a Layout
            if (isc.Layout && isc.isA.Layout(sibling.parentElement) &&
                sibling.parentElement.hasMember(sibling) &&
                sibling.parentElement.hasMember(currentWidget)) 
            {
                //this.logWarn("eliminated: " + sibling + 
                //             " because it is a fellow member of parent: " + 
                //             currentWidget +
                //             " within Layout: " + sibling.parentElement);
                continue;
            }
        
            // ignore TabBars within TabSets when we are in the paneContainer (one day
            // TabSet will probably become a Layout and this check will be redundant with the
            // above)
            if (isc.TabSet && isc.isA.TabBar(sibling) && 
                isc.isA.TabSet(sibling.parentElement) &&
                sibling.parentElement.paneContainer && 
                sibling.parentElement.paneContainer.contains(this)) 
            {
                //this.logWarn("eliminated TabBar: " + sibling + 
                //             " because we are in the paneContainer of TabSet: " +
                //             sibling.parentElement);
                continue;
            }

            
            if (sibling.containsPoint(x, y, false)) {      
                
                return false;
            }
        }
        currentWidget = currentWidget.parentElement;
    }
   
     
    return true;
},


//>	@method	canvas.scrollIntoView()
//			Scrolls the widget such that the passed in x / y coordinates (relative to the 
//          widget content) are visible in the viewport if they previously were not.
//
//		@group	positioning
//
//		@param	x		(number)	x-coordinate (relative to widget content)
//		@param	y		(number)	y-coordinate (relative to widget content)
//		@param	width   (number)	width of the rect to scroll into view - optional
//		@param	height  (number)	height of the rect to scroll into view - optional
//      @param  [xPosition] (string)    Where the target rectangle should show up in this
//                                      widget's viewport. Valid options are <code>"left"</code>
//                                      <code>"center"</code> or <code>"right"</code>. Defaults
//                                      to <code>"center"</code>.
//      @param  [yPosition] (string)    Where the target rectangle should show up in this
//                                      widget's viewport. Valid options are <code>"top"</code>
//                                      <code>"center"</code> or <code>"bottom"</code>. Defaults
//                                      to <code>"center"</code>.
//      @param  [animated]  (boolean)   If true, scrolling will be performed as an animation
//      @param [callback]   (callback)  Callback to fire when scrollIntoView completes. 
//                                      Typically would only be passed in for animated
//                                      scroll, but will be fired after synchronous scroll too.
//                                      Will also be fired if this method does not actually 
//                                      cause this widget's scroll position to change.<br>
//                                      Takes no parameters, but will be executed in the scope
//                                      of this widget.
//<
_$left:"left", _$top:"top", _$right:"right", _$bottom:"bottom", _$center:"center",
scrollIntoView : function (x,y, width, height, xPosition, yPosition, animated, callback) { 
    // If not passed a width / height, just scroll the point into view
    if (width == null) width = 0;
    if (height == null) height = 0;
    
    var synchronousCallback = true;

    var desiredScrollLeft, desiredScrollTop;
        
    if (this.overflow != isc.Canvas.VISIBLE && 
        this.overflow != isc.Canvas.IGNORE) {
    
        if (x != null) {
            var scrollLeft = this.getScrollLeft(),
                viewportWidth = this.getViewportWidth(),
                scrollRight = scrollLeft + viewportWidth,
                rightOff = false, 
                leftOff = false;
            
            if (x + width > scrollRight) rightOff = true;
            if (x < scrollLeft) leftOff = true;
            
            // if the right edge is off, or the left edge is off, but not both, we need to
            // scroll.
            // (If they're both off, on different sides, then the rect is greater than the
            // viewport and there's nothing we can do)
            if (rightOff != leftOff) {
                if (xPosition == this._$left) {
                    desiredScrollLeft = x;
                // Align the right edge with the right edge of the viewport
                } else if (xPosition == this._$right) {
                    desiredScrollLeft = (x + width) - this.getViewportWidth();

                // Centering is the default case
                } else {
                    desiredScrollLeft = (x + parseInt(width/2)) 
                                        - parseInt(this.getViewportWidth() / 2);
                }
            }
        } 
    
        if (y != null) {
            var scrollTop = this.getScrollTop(),
                scrollBottom = scrollTop + this.getViewportHeight(),
                topOff = false, 
                bottomOff = false;
                        
            if (y + height > scrollBottom) bottomOff = true;
            if (y < scrollTop) topOff = true;
            
            // if the top edge is off, or the bottom edge is off, but not both we need to
            // scroll.
            // (If they're both off, on different sides, then the rect is greater than the
            // viewport and there's nothing we can do)
            if (topOff != bottomOff) {
                if (yPosition == this._$top) {
                    desiredScrollTop = y;
                } else if (yPosition == this._$bottom) {
                    desiredScrollTop = (y + height) - this.getViewportHeight();
                } else {
                    desiredScrollTop = (y + parseInt(height/2))
                                         - parseInt(this.getViewportHeight() / 2);
                }
            }
        }
        // Note - if we don't have to scroll, avoid calling scrollTo, as this can take a little time
        if (desiredScrollLeft != null || desiredScrollTop != null) {
            //>Animation
            if (animated) {
                this.animateScroll(desiredScrollLeft, desiredScrollTop, callback);
                synchronousCallback = false;
            } else {
            //<Animation
            this.scrollTo(desiredScrollLeft, desiredScrollTop);
            //>Animation
            }
            //<Animation
        }
    }
    
    // At this point we may be done, or we may have parent elements whos viewports we're not
    // visible through.
    if (this.parentElement != null) {
        var parentLeft = x, parentTop = y;
        if (parentLeft != null) {
            parentLeft -= this.getScrollLeft();
            parentLeft += this.getOffsetLeft();
        }
        if (parentTop != null) {
            parentTop -= this.getScrollTop();
            parentTop += this.getOffsetTop();
        }
        
        this.parentElement.scrollIntoView(parentLeft, parentTop, width, height);
    }
    
    if (callback && synchronousCallback) this.fireCallback(callback);
},


//>	@method	canvas.intersects() ([])
//			Returns true if the rectangles of this widget and the specified widget overlap.
//      @visibility external
//		@group	positioning
//		@param	other		(canvas)	other canvas to test for intersection
//		@return	(boolean)	true if this canvas intersects other; false otherwise
//<
intersects : function (other){

	var otherLeft = other.getPageLeft(),
		otherWidth = other.getVisibleWidth(),
		otherTop = other.getPageTop(),
		otherHeight = other.getVisibleHeight()
	;
    return this.intersectsRect(otherLeft, otherTop, otherWidth, otherHeight)
},

//>	@method	canvas.intersectsRect() ([])
//			Returns true if the rectangle of this widget intersects with the rectangle coordinates
//          passed in, and false otherwise.
//      @visibility external
//		@group	positioning
//
//		@param	left		(number, array)	left coord of rect (or rect array)
//		@param	top 		(number)	    top coord of rect
//		@param	width		(number)	    width of rect
//		@param	height		(number)	    height of rect
//
//		@return	(boolean)	true if this canvas intersects the rectangle passed in; false otherwise
//<
intersectsRect : function (left, top, width, height){
    var rect1, rect2 = [];

    if (isc.isAn.Array(left)) rect1 = left;
    else rect1 = [left, top, width, height];

    return isc.Canvas.rectsIntersect(rect1, [this.getPageLeft(), this.getPageTop(),
                                             this.getVisibleWidth(), this.getVisibleHeight()]);
},

// Interior Coordinates
// --------------------------------------------------------------------------------------------

//>	@method	canvas.containsEvent()
//			Return true if the last event's mouse coordinates are within the bounds of this component.
//		NOTE: Z-ordering is not considered for the purposes of this test.  If the coordinate you're
//		testing is occluded by other component, but the X,Y coordiates are still within the bounds
//		of that component, this method will return true.
//
//		@group	events, positioning
//
//		@return	(boolean)	true if the event occurred within the bounds of this component
// @visibility external
//<
containsEvent : function () {
	return this.containsPoint(this.ns.EH.getX(), this.ns.EH.getY());
},

//>	@classMethod	canvas.getEventEdge()
//		Check if an event is within an "edge" of this canvas.
//
//		@group	dragdrop, dragResize
//
//		@param	[edgeMask]	(array)		Array of legal edges.  Default is all the resizeFrom mask of this canvas.
//
//      @see attr:canvas.resizeFrom
//		@return				(string)	"T", "TR", etc. for corner the event is within, or null if not within a legal edge.
//      @visibility external
//<
getEventEdge : function (edgeMask) {
	var EH = this.ns.EH;
	if (!edgeMask) edgeMask = (this.resizeFrom || EH.ALL_EDGES);
	var margin = this.edgeMarginSize;
	
	if (!isc.isAn.Array(edgeMask)) edgeMask = [edgeMask];

	// get various sizes, etc. to make the logic below cleaner
    // Note: coordinates reported are relative to outside our Margins. Adjust to get the 
    // coordinates over the widget's actual handle
    
    var margins = this._getSpecifiedMargins(),
        leftMargin = margins.left,
        rightMargin = margins.right,
        topMargin = margins.top,
        bottomMargin = margins.bottom;
    
    
	var left = this.getPageLeft() + leftMargin,		
		top = this.getPageTop() + topMargin,		
        // 2002.2.25 outset rect by 1 to fix problems in IE where exactly on the edge
        // doesn't register properly 
		right = (this.getPageRight() - rightMargin) + 1,	
		bottom = (this.getPageBottom() - bottomMargin) + 1,
		y = EH.getY(),
		x = EH.getX(),
		hEdge = "",
		vEdge = ""
	;
    
    //this.logWarn("x,y: " + [x,y] + ", rect: " + [left,top,right,bottom]);

	// if the mouse is not within this Canvas at all, bail
	if (y < top || y > bottom || x < left || x > right)	return null;
		
	// figure out what side/corner of the target we're in, if any

	// is it inside the top or bottom edge ?  (Bottom takes precedence over top)
	if 		(y >= (bottom - margin) && y <= bottom)			 	vEdge = "B";
	else if (y >= top 				&& y <= (top + margin + 1)) 	vEdge = "T";

	// is it inside the left or right edge ?  (Right takes precedence over left)
	if 		(x >= (right - margin)	&& x <= right) 				hEdge = "R";
	else if (x >= left 				&& x <= (left + margin + 1)) 	hEdge = "L";

	// if we're in some edge
	if (hEdge != "" || vEdge != "") {
		var resizeCorner = vEdge + hEdge;
        // figure out if we're in a valid corner, which takes precedence over an edge 
		if (edgeMask.contains(resizeCorner)) 	return resizeCorner;
		// not in a valid corner, check for valid edge (horizontal takes precedence)
		else if (hEdge != "" && edgeMask.contains(hEdge))		return hEdge;
		else if (vEdge != "" && edgeMask.contains(vEdge))		return vEdge;		
	}

	// no legal corner or edge found -- forget it!
	return null;
},


//>	@method	canvas.getOffsetX()
//	Return the X-coordinate of the last event relative to the left edge of the content of this
//	Canvas.<br><br>
//
//  NOTE: To get a coordinate relative to the <b>viewport</b> of this Canvas, subtract
//  this.getScrollLeft()
//
//	@group	events, positioning
//	@return	(number)	
//	@visibility external
//<
getOffsetX : function () {
    var value = this.ns.EH.getX()
        - (this.getPageLeft() + this.getLeftBorderSize()) 
        + this.getScrollLeft()
        // textDirection: if the canvas is drawn RTL and the vertical scrollbar is visible, it
        // will be on the left of the content, and we don't want to count it as part of the
        // canvas, so subtract the scrollbarSize from the offsetX
        - (this.vscrollOn && this.isRTL() ? this.getScrollbarSize() : 0);

    return value;
},


//>	@method	canvas.getOffsetY()
//	Return the Y-coordinate of the last event, relative to the top edge of the content of this
//	Canvas.<br><br>
//
//  NOTE: To get a coordinate relative to the <b>viewport</b> of this Canvas, subtract
//  this.getScrollTop()
//
//	@group	events, positioning
//	@return	(number)	
//	@visibility external
//<
getOffsetY : function () {
    return this.ns.EH.getY() 
                + this.getScrollTop() 
                - (this.getPageTop() + this.getTopBorderSize());
},


// Visible Area
// --------------------------------------------------------------------------------------------




//>	@method	canvas.setClip()	(A)
// Set the clip region of this handle
//
// NOTE: you can pass an array in TRBL order as the first parameter instead
//		
//		@group	sizing
//
//		@param	top			(number)	new top clip coordinate
//		@param	right		(number)	new right clip coordinate
//		@param	bottom		(number)	new bottom clip coordinate
//		@param	left		(number)	new left clip coordinate
//<
setClip : function (top, right, bottom, left) {

    // store the values in the 'clip' slot
    if (isc.isAn.Array(top))
        this._clip = top;
    else
        this._clip = [top, right, bottom, left];

    // if the layer has been drawn, set its clip!
    var clipHandle = this.getClipHandle();
    if (clipHandle != null) {

        var clip = this._clip;

        

         

        // actually set the clip
        clipHandle.style.clip = "rect("+ clip.join("px ")+"px)";
    } 
},

//>	@method	canvas.getScrollbarSize()	(A)
//  Returns the thickness of this widget's scrollbars.<br>
//  For canvases showing custom scrollbars this is determined from <code>this.scrollbarSize</code>
//
//	@group	scrolling
//	@return	(number) thickness of the scrollbars, in pixels
//	@visibility external
//  @see    scrollbarSize
//<
getScrollbarSize : function () {
    if (this.showCustomScrollbars) return this.scrollbarSize;
    return isc.Element.getNativeScrollbarSize();
},

//>	@method	canvas.getViewportWidth()	(A)
//  Returns the width of the viewport onto the scrollable content.
//
//	@group	sizing
//
//	@return	(number) width of the viewport, in pixels
//	@visibility external
//<
getViewportWidth : function() {
    return this.getVisibleWidth() - 
                (this.vscrollOn ? this.getScrollbarSize() : 0) -
                this.getHMarginBorder();
},

//>	@method	canvas.getViewportHeight()	(A)
//  Returns the height of the viewport onto the scrollable content.
//
//	@group	sizing
//
//	@return	(number) height of the viewport, in pixels
//	@visibility external
//<
getViewportHeight : function() {
    return this.getVisibleHeight() - 
                (this.hscrollOn ? this.getScrollbarSize() : 0) -
                this.getVMarginBorder();
},

//>	@method	canvas.getOuterViewportWidth()	(A)
// Returns the outer width of the viewport - the width including any borders (but excluding
// any vertical scrollbar)
//	@group	sizing
//
//	@return	(number) width of the viewport, in pixels
//<
getOuterViewportWidth : function () {
    
    return this.getVisibleWidth() - (this.vscrollOn ? this.getScrollbarSize() : 0) - 
           this.getHMarginSize();
},

//>	@method	canvas.getOuterViewportHeight()	(A)
// Returns the outer height of the viewport - the width including any borders (but excluding
// any horizontal scrollbar)
//
//	@group	sizing
//
//	@return	(number) height of the viewport, in pixels
//<
getOuterViewportHeight : function () {
    return this.getVisibleHeight() - (this.hscrollOn ? this.getScrollbarSize() : 0) - 
           this.getVMarginSize();
},



//>	@method	canvas.getInnerHeight()	(A)
//  Returns the amount of space available for (an) absolutely positioned child widget(s) or 
//  HTML content, without introducing clipping, scrolling or overflow.<br>
//  This is the space within the viewport of the widget (including padding, but excluding 
//  margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerWidth()
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerContentWidth()
//	@visibility external
//<
getInnerHeight : function() {
    return this.getHeight() 
           - ((this.hscrollOn || this.overflow == isc.Canvas.SCROLL) ? this.getScrollbarSize() 
                                                                     : 0) 
           - this.getVMarginBorder();
},

//>	@method	canvas.getInnerWidth()	(A)
//  Returns the amount of space available for (an) absolutely positioned child widget(s) or 
//  HTML content, without introducing clipping, scrolling or overflow.<br>
//  This is the space within the viewport of the widget (including padding, but excluding 
//  margins, borders or scrollbars) rendered at its specified size.
//
//	@return	(number) inner width of the widget in pixels
//	@group	sizing
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerContentWidth()
//  @visibility external
//<
getInnerWidth : function () {
    var width = this.getWidth();
    if (this.vscrollOn || this.overflow == isc.Canvas.SCROLL || this.alwaysShowVScrollbar)
        width -= this.getScrollbarSize();
    return width - this.getHMarginBorder();
},

//>	@method	canvas.getInnerContentHeight()	(A)
//  Returns the amount of space available for interior content (or relatively positioned child
//  widget(s)) without introducing clipping, scrolling or overflow.<br>
//  This is the space within the viewport of the widget (not including padding, and excluding 
//  margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerContentWidth()
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerWidth()
//	@visibility external
//<
getInnerContentHeight : function () {
    // Interior content space is the size of the handle (specified size less margins), minus 
    // border and padding -- the total available space for a relatively positioned HTML element 
    // without introducing overflow
    return this.getHeight() 
           - (this.hscrollOn || this.overflow == isc.Canvas.SCROLL ? 
                    this.getScrollbarSize() : 0) 
           - this.getVMarginBorderPad();

},


//>	@method	canvas.getInnerContentWidth()	(A)
//  Returns the amount of space available for interior content (or relatively positioned child
//  widget(s)) without introducing clipping, scrolling or overflow.<br>
//  This is the space within the viewport of the widget (not including padding, and excluding 
//  margins, borders or scrollbars) rendered at its specified size.
//
//	@group	sizing
//
//	@return	(number) inner height of the widget in pixels
//  @see Canvas.getInnerContentHeight()
//  @see Canvas.getInnerHeight()
//  @see Canvas.getInnerWidth()
//	@visibility external
//<
getInnerContentWidth : function () {
    var width = this.getWidth();
    if (this.vscrollOn || this.overflow == isc.Canvas.SCROLL || this.alwaysShowVScrollbar)
        width -= this.getScrollbarSize();
    return width - this.getHMarginBorderPad();

},
 

 
// Per-axis accessors for border, margin, padding size
// ---------------------------------------------------------------------------------------

//>	@method	canvas.getVBorderPad()	(A)
//  Returns the total size of vertical (top and bottom) border and padding for this widget.
//
//	@group	sizing
//
//	@return	(number) vertical border and padding for this widget
//<
getVBorderPad : function () {
    return this.getVBorderSize() + this.getVPadding();
},

//>	@method	canvas.getHBorderPad()	(A)
//  Returns the total size of horizontal (left and right) border and padding for this widget.
//
//	@group	sizing
//
//	@return	(number) horizontal border and padding for this widget
//<
getHBorderPad : function () {
    return this.getHBorderSize() + this.getHPadding();
},

getHMarginSize : function () {
    return this.getLeftMargin() + this.getRightMargin();
},

getVMarginSize : function () {
    return this.getTopMargin() + this.getBottomMargin();
},


getVMarginBorder : function () {
    var margins = this._calculateMargins(),
        borders = this._calculateBorderSize();
    return margins.top + margins.bottom + 
            borders.top + borders.bottom;
    //return this.getVMarginSize() + this.getVBorderSize();
},
getHMarginBorder : function () {
    var margins = this._calculateMargins(),
        borders = this._calculateBorderSize();
    return margins.left + margins.right + 
            borders.left + borders.right;
    //return this.getHMarginSize() + this.getHBorderSize();
},

getVMarginBorderPad : function () {
    return this.getVMarginSize() + this.getVBorderPad();
},

getHMarginBorderPad : function () {
    return this.getHMarginSize() + this.getHBorderPad();
},

// Visible Dimensions
// ---------------------------------------------------------------------------------------

//>!BackCompat 2004.1.1 outdated synonyms of getVisibleHeight/Width
getClipWidth : function () { return this.getVisibleWidth(); },
getClipHeight : function () { return this.getVisibleHeight(); },
//<!BackCompat

//>	@method	canvas.getVisibleWidth()	(A)
//      Return the visible width of the Canvas.
//
//		@group	sizing
//
//		@return	(number) visible width in pixels
//  @visibility external
//<
// Note this width includes any margin for the item - essentially it's the space required to
// render the widget

getVisibleWidth : function (recalc) {
    if ((this._drawn || this._handleDrawn) && 
        (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V)) {
        // if the overflow is visible, the visible width may be greater than the 
        // specified width
        return Math.max(this.width, 
                        (this.getScrollWidth(recalc) + this.getHMarginBorder()));
    } else {
        // overflow is Hidden, Auto, Scroll, CLIP_H or Ignore.
        // Return the specified width
        //>Animation
        // If we're doing an animated hide/show verify adjust for rendered scrollbar size if
        // necessary
        var animationInfo = this.isAnimating(this._$show) ? this.$showAnimationInfo :
                            this.isAnimating(this._$hide) ? this.$hideAnimationInfo : null;
        if (animationInfo != null && !animationInfo._vertical && this.vscrollOn) {
            var sbDelta = 0;
            if (this.vscrollbar.visibility == isc.Canvas.HIDDEN) {
                sbDelta = this.getScrollbarSize();
            } else {
                sbDelta = this.getScrollbarSize() - this.vscrollbar.getWidth();
            }
            return Math.max(this.getWidth() - sbDelta,1);
        }
        //<Animation
        return this.getWidth();
    }
},

//>	@method	canvas.getVisibleHeight()	(A)
//      Return the visible height of the Canvas.
//
//		@group	sizing
//
//		@return	(number) visible height in pixels
//  @visibility external
//<
getVisibleHeight : function (recalc) {
    if ((this._drawn || this._handleDrawn) && 
        (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H)) 
    {
        // if the overflow is visible, the visible height may be greater than the 
        // specified height
        return Math.max(this.getHeight(), 
                        (this.getScrollHeight(recalc) + this.getVMarginBorder()));
                        
    } else {
        //>Animation
        // During animateShow() / animateHide(), with wipe/slide effect, we resize and hide the
        // scrollbar on the leading edge of the resize.
        // setHeight() assumes the scrollbar is fully visible, so at this point the specified height
        // could exceed the rendered height by the size of the scrollbar.
        // Explicitly catch this case and return the smaller size.
        // This ensures that when animateHide() / animateShow()ing members of a layout the reflow
        // respects tha actual space taken up by the member during the animation rather than being
        // off by up to one scrollbarSize
        var animationInfo = this.isAnimating(this._$show) ? this.$showAnimationInfo :
                            this.isAnimating(this._$hide) ? this.$hideAnimationInfo : null;
        if (animationInfo != null && animationInfo._vertical && this.hscrollOn) {
            var sbDelta = 0;
            if (this.hscrollbar.visibility == isc.Canvas.HIDDEN) {
                sbDelta = this.getScrollbarSize();
            } else {
                sbDelta = this.getScrollbarSize() - this.hscrollbar.getHeight();
            }
            return Math.max(this.getHeight() - sbDelta,1);
        }
        //<Animation
        // overflow is Hidden, Auto, Scroll, CLIP_V, or Ignore.
        // Return the specified height
        return this.getHeight();
    }
},

getPeerRect : function () {
    var rect = this.getPageRect();
    if (this.peers == null) return rect;
    for (var i = 0; i < this.peers.length; i++) {
        var peer = this.peers[i];
        // NOTE: only ignore a peer that is explicitly not visible while we are visible
        if (!peer.isDrawn() || (this.isVisible() && !peer.isVisible())) continue;
        
        // Special case: If we are hidden, and we have hidden scrollbars, they may or may not
        // show with us.
        // Furthermore, when we adjustOverflow(), if scrollbars are no longer required we hide
        // them, but don't bother resizing them (if thats required), so they can effect the
        // size reported by this method when they shouldn't
        // Use vscrollOn / hscrollOn to avoid taking them into account if they're not currently 
        // meant to be showing.
        if ((!this.vscrollOn && peer == this.vscrollbar) || 
            (!this.hscrollOn && peer == this.hscrollbar)) continue;

        var peerRect = peer.getPageRect();
        
        if (peerRect[0] < rect[0]) rect[0] = peerRect[0];
        if (peerRect[1] < rect[1]) rect[1] = peerRect[1];
        // NOTE: a peer may extend to the right/bottom while being smaller than it's master
        var peerRight = peerRect[0] + peerRect[2];
        if (peerRight > rect[0] + rect[2]) rect[2] = peerRight - rect[0];
        var peerBottom = peerRect[1] + peerRect[3];
        if (peerBottom > rect[1] + rect[3]) rect[3] = peerBottom - rect[1];
    }
    return rect;
},

// Moving
// --------------------------------------------------------------------------------------------


//>	@method	canvas.moveBy() ([])
//			Moves the widget deltaX pixels to the right and deltaY pixels down. Pass negative
//          numbers to move up and/or to the left.
//      @visibility external
//		@group	positioning
//		@param	deltaX		(number)	amount to move horizontally (may be negative)
//		@param	deltaY		(number)	amount to move vertically (may be negative)
//		@return	(boolean)	whether the component actually moved
//      @example    move
//<
//>Animation
// @param [animating] (boolean) Internal parameter passed if this move is being called as part
//  of an animation
//<Animation

moveBy : function (deltaX, deltaY, animating, resizeHandle) {
    //>Animation
    // If an external moveBy is called during an animated setRect, finish the animated setRect
    // before starting the explicit move.
    // Note: it's a setRect if resizeHandle is true, and a straight move otherwise
    var setRectAnimating = animating && resizeHandle;
    if (!setRectAnimating && this.rectAnimation) this.finishAnimation("rect");
    else if (!animating && this.moveAnimation) this.finishAnimation("move");
    //<Animation
    
    if (isc._traceMarkers) arguments.__this = this;

	// adjust our internal values by values passed in
	if (isc.isA.Number(deltaX)) 
		this.left += deltaX;
	else 
		deltaX = 0;

	if (isc.isA.Number(deltaY)) 
		this.top += deltaY;
	else 
		deltaY = 0;
 
    
    var moved = (deltaX != 0 || deltaY != 0);
    if (!moved && !resizeHandle) return false;
    
    // store the deltas locally - used by _completeMoveBy()
    this._moveDeltaX = deltaX;
    this._moveDeltaY = deltaY;
    
    
    var width = (resizeHandle && this._resizeDeltaX ? this.width : null),
        height = (resizeHandle && this._resizeDeltaY ? this._height : null);

    this._setHandleRect(this.left, this.top, width, height);
    if (resizeHandle) this._completeResizeBy();
    this._completeMoveBy();
    
    return moved;
},


_completeMoveBy : function () {
    
    var deltaX = (this._moveDeltaX || 0),
        deltaY = (this._moveDeltaY || 0),
        undef;
   
    this._moveDeltaX = undef;
    this._moveDeltaY = undef;

    // Just bail if this method was called with no move required.
    
    if (!deltaX && !deltaY) return;

    // fire up/down chain parent and child / master and peer notifications
    this._fireParentMoved(this, deltaX, deltaY);
    this._fireMasterMoved(deltaX, deltaY);
    if (this.parentElement) this.parentElement.childMoved(this, deltaX, deltaY);
    if (this.masterElement) this.masterElement.peerMoved(this, deltaX, deltaY);

    //>FocusProxy If we have a focusProxy written into the DOM, move it so it continues to
    // float over this widget.
    if (this._useFocusProxy && this._hasFocusProxy) {
        var fpp = this._getFocusProxyParentHandle();
        if (fpp != null) {
            var newLeft = parseInt(fpp.style.left) + deltaX,
                newTop = parseInt(fpp.style.top) + deltaY;
            fpp.style.left = newLeft + "px";
            fpp.style.top = newTop + "px";
        }
    } //<FocusProxy
    
    
    
    // call the observable moved method
    this.moved(deltaX, deltaY);
},

//>	@method	canvas.moved()
//  Observable method called whenever a Canvas is explicitly moved.
//  Default implementation is to call parentElement.childMoved()
//<
moved : function (deltaX, deltaY) {
//!DONTOBFUSCATE  (we want observers to be able to pick up the passed values)
},

// If our parent has moved, inform any children we have that an ancestor has moved.
// This notifies the children that they will have been repositioned in terms of page 
// coordinates.
parentMoved : function (parent, deltaX, deltaY) { 
    // fire recursively through our children
    this._fireParentMoved(parent, deltaX, deltaY); 
},
_fireParentMoved : function (parent, deltaX, deltaY) {
    var children = this.children;
    if (children == null) return;
    for (var i = 0; i < children.length; i++) {
        // NOTE: this fires before during init, before children have necessarily been
        // auto-created
        if (isc.isA.Canvas(children[i])) children[i].parentMoved(parent, deltaX, deltaY);
    }
},

// parent receiving notification that a child has moved
_$childMoved : "childMoved",
childMoved : function (child, deltaX, deltaY) {
    // if a child moves, the size of our content may have changed, so adjustOverflow.  For
    // example, we may need to grow/shrink to fit (overflow:visible), or show or hide scrollbars
    // (overflow:auto).
    
    if (this.allowContentAndChildren && this.overflow == isc.Canvas.VISIBLE)
        this._resetHandleOnAdjustOverflow = true;
    
    this._markForAdjustOverflow(this._$childMoved);
},

_fireMasterMoved : function (deltaX, deltaY) {
    var peers = this.peers;
    if (peers == null) return;
    for (var i = 0; i < peers.length; i++) {
        if (peers[i]) peers[i].masterMoved(deltaX, deltaY);
    }
},
masterMoved : function (deltaX, deltaY) {
    if (this._moveWithMaster) this.moveBy(deltaX, deltaY);
    // NOTE: not a recursive notification
},

// master receiving notification that a peer has moved
peerMoved : function (child, deltaX, deltaY) { },

//> @method canvas.dragReposioned()    (A)
// Observable function fired once at the end of a successful drag-reposition operation.
// Useful for firing some action in response to reposition without firing repeatedly on every
// dragMove while the user is drag-resizing the target.
//<
dragRepositioned : function () {},

// Percent / "*" coordinate handling
// --------------------------------------------------------------------------------------------
// Special coordinate specifications like percents and "*" must get resolved into pixel values
// before the widget is drawn.
// We handle this by resolving these coordinates to pixel values on widget init(), (or on 
// setWidth() / setHeight()), and storing the pixel value as this.width (available via 
// this.getWidth()).
// The original string value is stored in a second variable and updated when it's meaning 
// changes (eg a percent's resolved value changing on parent/page resize).
//   - percents are resolved as percents of the parent size, or of the page size if we're at top
//     level
//   - "*" values are just destroyed, since they only matter in Layouts, and are the same as the
//     absence of a value

// get the delta between the coordinate of name 'name', new value 'coord', current value 'current
// value'.  Handles resolving percent coordinates to pixel values and discarding and logging bad
// values
_$height : "height",
_$width : "width",
_$left : "left",
_$top : "top",
_$_height: "_height",
_$percent: "%",
_$star : "*",
_percentNames : {
    height : "_percent_height",
    width : "_percent_width",
    left : "_percent_left",
    top : "_percent_top"
},
getDelta : function (name, newValue, currentValue) {
    if (newValue == null) return null;
    
    
    var propertyName = name,
        percentName = this._percentNames[name];
    if (name == this._$height) propertyName = this._$_height;
    
    // If we were passed a fractional number, round it and warn.
    // Note we don't need to do this with percent values, which already get rounded
    // or numbers-as-strings (like "5") where we simply parseInt when converting.
    if (isc.isA.Number(newValue)) {
        var rounded = Math.round(newValue);
        if (rounded != newValue) {
            this.logWarn(name + " specified as fractional coordinate:"+ newValue +
                        ". Rounded to:" + rounded);
            newValue = rounded;
        }
    } else if (isc.isA.String(newValue) && isc.endsWith(newValue, this._$percent)) {
        
        // remember the percent version of this coordinate
        this[percentName] = newValue;

        
        // if this is a top-level widget with a percent coordinate, update whenever there is a
        // page resize event.  NOTE: this is FIRE_ONCE so we don't receive multiple resize
        // events; we reregister each time.
        if (this.masterElement == null && this.parentElement == null && this._resizeID == null) {
            this._resizeID = isc.Page.setEvent(this._$resize, this, isc.Page.FIRE_ONCE);
        }

        if (this._canvas_initializing) {
            // at init time only, ensure we report a non-zero delta as we resolve our
            // percentage size to a pixel size.  We are effectively going from an
            // unknown to a known size, so we want to trigger all logic associated
            // with size change.  Subsequently, a percent size widget reports normal deltas.
            currentValue = this[propertyName] = 0;
            
            if (this.percentBox == "custom") this[propertyName] = 1;
        }
        
        // "custom" percentBox - assume the parent will apply some custom logic to size / position
        // this child so suppress the standard handling
        if (this.percentBox == "custom") return 0;

        // get the relevant full size
        // this is the page width/height if this canvas has no parents, or
        // the parent element's inner width/height, otherwise
        var parent, fullSize, insideParent,
            horizontal = (name == this._$left || name == this._$width);
        // viewport vs outer size determined by percentBox setting
        if (this.percentSource || (this.snapTo && this.masterElement)) {
            parent = this.percentSource || this.masterElement;            
            insideParent = (this.percentBox == this._$viewport), 
            fullSize = horizontal ? (insideParent ? parent.getViewportWidth() 
                                                  : parent.getVisibleWidth())
                                  : (insideParent ? parent.getViewportHeight() 
                                                  : parent.getVisibleHeight());
        } else {
            parent = this.parentElement; 
            fullSize = (horizontal ? (parent ? parent.getInnerWidth() : isc.Page.getWidth())
                                   : (parent ? parent.getInnerHeight(): isc.Page.getHeight())
                       );
        }
        
        //>IE
        if (isc.Browser.isIE && !isc.Page.isLoaded() && 
            ((isc.Page.getWidth() == 0) || (isc.Page.getHeight() == 0))) 
        {
            isc.Page.setEvent("load", this.ID + ".pageResize()", isc.Page.FIRE_ONCE);
            // set a flag to indicate this special case so we avoid attempting to draw() before
            // we've resized correctly
            this._pendingPageResizeForModalDialog = true;
        } //<IE
        
        // compute the coord as a percent of that 
        
        newValue = Math.round((parseInt(newValue, 10) / 100) * fullSize);

        //if (name == "height") {
        //    this.logWarn("resolved percent height ["+ this[percentName]+ "] to: " + newValue + 
        //                 ", parent height: " + fullSize + ", currentValue: " + currentValue);
        //}
        return newValue - currentValue;
    }

    // handle coordinates specified as strings.  Even though we document this as incorrect, it's
    // really easy to forget if you work in XML.
    var origNewValue = newValue;
    if (!isc.isA.Number(newValue)) {
        newValue = parseInt(newValue);
        // if we parsed the newValue and got a valid number, and this is init time
        // (currentValue is a string, which can never happen after init), change the saved
        // value to the numeric version.
        if (isc.isA.Number(newValue) && isc.isA.String(currentValue)) {
            this[propertyName] = currentValue = newValue;
        }
    }

    // clear any previously defined percent size -- this is either a valid numeric size, or an
    // invalid value, in which case we'll revert to default size.
    this[percentName] = null;
        
    // complain about bad coordinates.
    if (!isc.isA.Number(newValue) || 
        (newValue < 0 && (name == this._$width || name == this._$height))) 
    {
        // HACK: avoid complaining about "*", which is valid within a Layout, and which can be
        // treated as the absence of a value.
        if (origNewValue != "*") {
            //>DEBUG
            this.logWarn("ignoring bad or negative " + name + ": " + origNewValue +
                         (this.logIsDebugEnabled("sizing") ? this.getStackTrace() 
                          : " [enable 'sizing' log for stack trace]")); //<DEBUG
        } else {
            // HACK: setting width/height to "*" after init:
            // - this should mean the same thing "*" does before init, and the Layout normally
            //   picks up the "*" size via _userWidth/Height, since getWidth()/Height() always
            //   return pixels
            // - there is no clear delta value we can report - even if we reverted to default
            //   size there may be no change - so the usual childResized() notification that
            //   causes automatic reflow won't occur.  So we do it manually.
            //this.logWarn("clearing user prop: " + name);
            name == this._$width ? this._userWidth = "*" : this._userHeight = "*";
            var parent = this.parentElement;
            if (isc.isA.Layout(parent) && parent.hasMember(this)) {
                parent.reflow(this.getID() + " set " + name + " to '*'");
            }
        }

        // if the value we were initialized with is bad, remove it, hence reverting to
        // defaults.  NOTE: this doesn't affect Layouts, which look under the special
        // _userWidth/Height variable to find the explicitly specified width/height
        if (currentValue == this[name] || currentValue == this[propertyName]) {
            currentValue = this.restoreDefaultSize(name == this._$height);
        }
        // Fire adjustOverflow to actually resize the handle to the default size, if necessary
        this.adjustOverflow();

        return null;
    }

    //this.logWarn("getDelta: newValue: " + newValue + ", currentValue: " + currentValue);

    return newValue - currentValue;
},

restoreDefaultSize : function (isHeight) {
    
    var propertyName = isHeight ? this._$height : this._$width,
        instanceDefault = this.getClass().getInstanceProperty(propertyName);

    // use defaultHeight/Width if set
    if (!isc.isA.Number(instanceDefault)) {
        if (isHeight) instanceDefault = this.defaultHeight;
        else instanceDefault = this.defaultWidth;
    }

    var currentValue = this[propertyName] = (isc.isA.Number(instanceDefault) ? 
                                             instanceDefault : 0);
                                             
    if (isHeight) this._height = currentValue;

    return currentValue;
},

// if we have any percent coordinates, recompute their values
pageResize : function () {
    //this.logWarn("pageResize: resizing to: " + [this._percentWidth, this._percentHeight] + " of " +
    //             [Page.getWidth(), Page.getHeight()] + this.getStackTrace());
    this._resizeID = null;
    // clear out the flag set up for handling the 'showModalDialog' case in IE
    this._pendingPageResizeForModalDialog = null;
    this._resolvePercentageSize();
},

//>	@method	canvas.moveTo() ([])
// Moves the widget so that its top-left corner is at the specified coordinates. 
// <P>
// This method will also accept a single parameter as an object array with left and top given
// as properties.
//
//      @visibility external
//		@group	positioning
//		@param	[left]		(number or Object) x-coordinate to move to in LOCAL coordinates
//						                       or Object with left and top properties
//		@param	[top]		(number)	y-coordinate to move to in LOCAL coordinates
//		@return	(boolean)	whether the component actually moved
//      @example    move
//<
//>Animation
// @param [animating] (boolean) optional internal parameter passed if this moveTo is being 
//   called as part of an animation.
//<Animation

moveTo : function (left, top, animating, resizeHandle) {

    if (!resizeHandle && left == null && top == null) return false;
    if (isc._traceMarkers) arguments.__this = this;

    if (left != null && left.top != null) {
        top = left.top;
        left = left.left;
    }

    var deltaX = this.getDelta(this._$left, left, this.getLeft()),
        deltaY = this.getDelta(this._$top, top, this.getTop());

    //if (deltaX != 0 || deltaY != 0) {
    //    this.logWarn("moveTo: " + [x,y] + " calling moveBy: " + [deltaX, deltaY] + 
    //                 ", top: " + this.getTop() + ", scrollTop: " + this.scrollTop +
    //                 ", left: " + this.getLeft() + ", scrollLeft: " + this.scrollLeft +
    //                 ", using CSS scrolling: " + this.usingCSSScrollbars());
    //}

	// ... and call the moveBy function to do it for us
	return this.moveBy(deltaX, deltaY, animating, resizeHandle);
},


//>	@method	canvas.moveToEvent()
//			move to the last event location (such as when we're being dragged around)
//		@group	positioning, events
//		@param	[offsetX]		(number)	x-coordinate offset (typically used for drag and drop)
//		@param	[offsetY]		(number)	y-coordinate offset (typically used for drag and drop)
//<
moveToEvent : function (offsetX, offsetY) {

    // get the global coordinates of the event, maintaining the drag offset
    var event = this.ns.EH.getLastEvent(),
		x = event.x,
		y = event.y
	;    
    
    if (isc.isA.Number(offsetX)) x -= offsetX;	    
    if (isc.isA.Number(offsetY)) y -= offsetY;
    // Snap-to-grid 
    
    var EH = this.ns.EH;
    var snapChild = EH.getDragTarget(event);
    var snapParent;
    if (EH.getDragTarget().canDrop) {
        snapParent = EH.getDropTarget(event);
        if (snapParent) {
            
            if ( ! snapChild.snapOnDrop || ! snapParent.shouldSnapOnDrop(snapChild) ) {
                snapParent = null;  // Effectively switches off snap-to-grid
            }
        } else {
            snapParent = EH.getDragTarget(event).parentElement;
        }
    } else {
        snapParent = EH.getDragTarget(event).parentElement;
    }
    
    // Parentless canvases cannot participate in snap-to-grid
    if (isc.isA.Canvas(snapParent) && 
        (snapChild.snapToGrid == true || 
            (snapChild.snapToGrid == null && snapParent.childrenSnapToGrid == true)))
    {
        // Support suppressing the drag offset.
        // This is used in GridRenderer where we want the drag child to snap to whatever
        // cell the mouse is regardless of original drag offset
        if (snapParent.noSnapDragOffset(this)) {
            x = event.x,
            y = event.y
        }
        if (snapParent.snapAxis == isc.Canvas.HORIZONTAL ||    
            snapParent.snapAxis == isc.Canvas.BOTH) 
        {
            var snapParentContentOffset = 
                (snapParent.getPageLeft() + snapParent.getLeftBorderSize() +
                  snapParent.getLeftMargin() - snapParent.getScrollLeft()); 
            x -= snapParentContentOffset; 
            x = snapParent.getHSnapPosition(x) + snapParent.getHSnapOrigin(snapChild);
            x += snapParentContentOffset;
        }
        if (snapParent.snapAxis == isc.Canvas.VERTICAL ||    
            snapParent.snapAxis == isc.Canvas.BOTH)
        {
            var snapParentContentOffset =
                 (snapParent.getPageTop() + snapParent.getTopBorderSize() + 
                  snapParent.getTopMargin() - snapParent.getScrollTop())
            y -= snapParentContentOffset;
            y = snapParent.getVSnapPosition(y) + snapParent.getVSnapOrigin(snapChild);
            y += snapParentContentOffset;
        }
    }

    // x/y is where we want to move to in global coordinates, so use setPageRect 
    // (Don't pass in width and height - will just move to page coordinates)
    this.setPageRect(  x, y );
},

//> @method canvas.getVSnapOrigin()
// Get an offset to be used when calculating snap positioning. Returns 0 by default.
//
// @param [snapChild] (Canvas) the child that is being snapped
// @return (integer) The offset to use when snapping
//
// @group positioning
// @see canvas.getVSnapPosition 
// @visibility external
//<
getVSnapOrigin : function (snapChild) {
    return this.VSnapOrigin ? this.VSnapOrigin : 0;    
},

//> @method canvas.getHSnapOrigin()
// Get an offset to be used when calculating snap positioning. Returns 0 by default.
//
// @param [snapChild] (Canvas) the child that is being snapped
// @return (integer) The offset to use when snapping
//
// @group positioning
// @see canvas.getHSnapPosition
// @visibility external
//<
getHSnapOrigin : function (snapChild) {
    return this.HSnapOrigin ? this.HSnapOrigin : 0;    
},

//>	@method	canvas.placeNextTo()
//  Move this canvas so that it is directly next to another canvas, unless that would cause 
//  this canvas to extend beyond the browser window in some direction, in which case this
//  canvas should be placed such that it doesn't extend beyond the browser viewport.
//      @group	positioning, events
//      @param	otherWidget (Canvas)    Canvas to move next to
//      @param  [side]  (string)    Which side of the other canvas should we put. Options are
//                                  "top", "bottom", "left", "right". (Defaults to "bottom")
//      @param  [canOcclude]    (boolean)   
//          This property controls whether this canvas can be positioned on top of the other 
//          widget if there isn't room to put it next to the other widget without going off
//          screen.<br>
//          If 'canOcclude' is true, simply shift this widget over the other widget, so that 
//          it ends up onscreen.  If 'canOcclude' is false, avoid extending offscreen 
//          by positioning this widget on the other side of the other widget.
//      @param  [otherAxisAlign]    (string)    Can be one of "left", "right", "outside-left",
//                                          "outside-right", "top", "bottom", "outside-top",
//                                          "outside-bottom". (Defaults to "left" if side is
//                                          "top" or "bottom", "top" if side is "left" or 
//                                          "right").<br>
//                                          This property determines how this widget will be
//                                          aligned with the other widget on the other axis.
//<
placeNextTo : function (otherWidget, side, canOcclude, otherAxisAlign) {
    // Pick up defaults for side, canOcclude, otherAxisAlign from _placeRect
    var adjacentRect = otherWidget.getPeerRect(),
        thisRect = this.getPeerRect(),
        pos = isc.Canvas._placeRect(
                thisRect[2], thisRect[3],
                adjacentRect, side, canOcclude, otherAxisAlign
              )
    ;
    
    this.setPageRect(pos[0], pos[1]);
},

//> @method canvas.showNextTo()
// Show this widget next to another widget.
// @param otherWidget (Canvas) Canvas to show next to
// @param [side] (String) which side to show on, defaults to "right"
//
// @visibility external
//<
showNextTo : function (otherWidget, side, canOcclude) {
    if (side == null) side = "right";
    if (canOcclude == null) canOcclude = false;
    this.placeNextTo(otherWidget, side, canOcclude);
    this.animateShow("fade");
},

//>	@method	canvas.placeNear()
//  Move this canvas to the specified point, or as close to the specified point as possible 
//  without this widget extending beyond the edge of the browser viewport on any side.
//      @group	positioning, events
//      @param	[left]  Left coordinate (defaults to mouse position)
//      @param  [top]   Top coordinate  (defaults to mouse position)
//<
placeNear : function (left, top) {
    if (isc.isAn.Array(left)) {
        top = left[1]; left = left[0]; 
    } else if (isc.isAn.Object(left)) {
        top = left.top; left = left.left;
    }
 
    var thisRect = this.getPeerRect(),
        pos = isc.Canvas._placeRect(
                    thisRect[2], thisRect[3], {left:left, top:top}
              );    
    this.setPageRect(pos[0], pos[1]);
},



// Resizing
// --------------------------------------------------------------------------------------------

//>	@method	canvas.resizeBy()   ([])
//			Resizes the widget, adding deltaX to its width and deltaY to its height (moves the right
//          and/or bottom sides of the widget).
//		@group	sizing
//		@param	[deltaX]	(number)	amount to resize horizontally (may be negative)
//		@param	[deltaY]	(number)	amount to resize vertically (may be negative)
//		@return	(boolean)	whether the component actually changed size
//      @visibility external
//      @example    resize
//<
// @param [animating] (boolean) Internal optional parameter indicating that this resize is
//  ocurring as part of an animation
// @param [suppressHandleUpdate] (boolean) If passed avoid actually updating the handle
resizeBy : function (deltaX, deltaY, animating, suppressHandleUpdate) {
    if (isc._traceMarkers) arguments.__this = this;

    //>Animation
    // If an external resizeBy is called during an animated setRect, finish the animated setRect
    // before starting the explicit resize.
    // Note: setRect will pass the suppressHandleUpdate param
    var setRectAnimating = animating && suppressHandleUpdate;
    if (!setRectAnimating && this.rectAnimation) this.finishAnimation("rect");
    if (!animating) {
        // If we're doing a setRect animation, kill any running resizeAnimation
        if (setRectAnimating && this.resizeAnimation) this.finishAnimation("resize");
        // animated show / hide also do a resize.
        if (this.hideAnimation) this.finishAnimation("hide");
        if (this.showAnimation) this.finishAnimation("show");
    }
    //<Animation
    
    

    var oldWidth = this.getWidth(), oldHeight = this.getHeight();
	// adjust width and height by the values passed in
    
	if (isc.isA.Number(deltaX)) {
		this.width += deltaX;
        // set a marker for Layouts (not yet used)
        if (!this._canvas_initializing) this._widthSetAfterInit = true;
	} else {
		deltaX = 0;
    }

	if (isc.isA.Number(deltaY)) {
        
		this.height = this._height = oldHeight + deltaY;
        // set a marker for Layouts (not yet used)
        if (!this._canvas_initializing) this._heightSetAfterInit = true;
	} else {
		deltaY = 0;
    }

    // no-op.  This is very important as generally most Canvii redraw if they are resized, and
    // layout code is very likely to blindly call resizeTo() in no-op situations.
    // NOTE: it's possible to fool a Canvas into not resizing when it needs to resize, by
    // setting the width/height properties directly without calling setters, then calling a
    // move/resize function with the current values, which causes the Canvas to believe there
    // has been no change in size.  This just means that you really have to call the setter
    // functions, as it's critical to be able to no-op here!
    if (deltaX == 0 && deltaY == 0) return false;

    // Store the delta's locally - used by _completeResizeBy
    // - will be cleared out when we actually resize the handle.
    this._resizeDeltaX = deltaX;
    this._resizeDeltaY = deltaY;
    
    // Also store whether we're animating or not - required by completeResizeBy
    this._resizeAnimating = animating;

    if (this.isDrawn() && this.logIsInfoEnabled(this._$resize)) {
        this.logInfo("resize of drawn component: " +
                     "new width/height: " + [this.width, this._height] + 
                     ", old width/height: " + [oldWidth, oldHeight] + 
                     ", delta width/height: " + [deltaX, deltaY] +
                     (this.logIsDebugEnabled(this._$resize) ? 
                      this.getStackTrace() : ""), this._$resize);
    }

    // we don't fire resized() if suppressHandleUpdate is true - this will be called from from
    // moveBy()
    // This ensures that when resized() is fired the handle has actually been resized. 
    
    if (!suppressHandleUpdate) {

        // if we have a clip region set, it will have been clobbered by _setHandleRect.
        // restore it:
        // Note: since we're resizing from the top left (bottom / right will 'move'), adjust
        // those coords of the clip by the amount we've resized.
        var clip = this._clip;
        if (isc.isAn.Array(clip)) {
            clip[1] += deltaX;
            clip[2] += deltaY;
        }

        var drawnState = this.getDrawnState();
        if (drawnState == isc.Canvas.COMPLETE) {
            // actually resize the handle by calling _setHandleRect
            this._setHandleRect(this.left, this.top, this.width, this._height);

            if (isc.isAn.Array(clip)) this.setClip(clip);
            
        // If we've already got our tag start but haven't finished drawing, when we write it out 
        // the handle will be the wrong size.
        // Set a flag so that when we *are* done drawing we resize our handle before adjusting 
        // overflow
        } else if (drawnState != isc.Canvas.UNDRAWN) {
            this._resizeHandleOnDrawComplete = true;
        }
        this._completeResizeBy();
    }
    
	// return true indicating that a resize actually occurred (as opposed to a no-op of staying
    // the same size)
    return true;
},

_$resized: "resized",
_completeResizeBy : function () {
    var deltaX = (this._resizeDeltaX || 0),
        deltaY = (this._resizeDeltaY || 0),
        animating = this._resizeAnimating,
        undef;
        
    this._resizeDeltaX = undef;
    this._resizeDeltaY = undef;
    this._resizeAnimating = undef;
    
    // Bail if the delta is zero or null
    
    if (!deltaX && !deltaY) return;
    
    var redrawOnResize;
    if (this.isDrawn()) {
        // check if we're supposed to redraw on resize
        
        redrawOnResize = this.shouldRedrawOnResize(deltaX, deltaY, animating);
        // if we're supposed to redraw when resized, mark for a redraw
        if (redrawOnResize) {
            //if (this.isDrawn()) {
            //    this.logWarn("redrawing due to resize: " +
            //                 "old width/height: " + [oldWidth, oldHeight] + 
            //                 ", delta width/height: " + [deltaX, deltaY]);
            //}            
            this.markForRedraw(this._$resize);
        }
    }
    
	// run layout code to resize children, if any.  Note this needs to happen before we
    // adjustOverflow.
	if (!animating) this.layoutChildren(this._$resized, deltaX, deltaY)

    if (isc.Browser.isMoz && this.containsIFrame()) this._sizeIFrame();

        
    this._handleResized(deltaX, deltaY);

	// if we're not going to redraw, which would adjust overflow automatically, we need to adjust
    // now.
    if (!redrawOnResize) this.adjustOverflow(this._$resize);

    //>FocusProxy
    // If we're showing a focus proxy, resize it to match our new (specified) size, so that
    // when the user tabs into the focus proxy, the whole widget gets scrolled into view.
    
    if (!animating && this._useFocusProxy && this._hasFocusProxy) {
        var fp = this._getFocusProxyHandle();
        if (fp != null) {
            fp.style.width = this.getWidth() + isc.px;
            fp.style.height = this.getHeight() + isc.px;
        }
    }
    //<FocusProxy
    
    // tell our peers to resize as well
    this.resizePeersBy(deltaX, deltaY);
    // call the observable resized method
    this._resized(deltaX, deltaY);
},

shouldRedrawOnResize : function (deltaX, deltaY) {
    var redrawOnResize = this.redrawOnResize;
    if (redrawOnResize == null) {
        

        // this Canvas doesn't need to redraw if.. 
        redrawOnResize = !(
            // it's a parent with no content
            (this.children != null && this.children.length > 0 && 
             !this.allowContentAndChildren) ||
            // contents are static: getInnerHTML has not been overriden, this.contents has not
            // been set to a function
            (this.getInnerHTML == isc.Canvas._instancePrototype.getInnerHTML && 
             !isc.isA.Function(this.contents)));
    }
    return redrawOnResize;
},

//> @method canvas.dragResizing()
// Returns true if this widget is currently being drag-resized.
//<
dragResizing : function () {
    var EH = isc.EH;
    return (EH.dragging && EH.dragOperation == EH.DRAG_RESIZE && EH.dragTarget==this);
},


// _resized() - calls public, observable resized() method. 
_resized : function (deltaX, deltaY, reason) {
    if (isc._traceMarkers) arguments.__this = this;
//!DONTOBFUSCATE  (we want observers to be able to pick up the passed values)

    // rerun snapTo positioning for cases where size affects positioning (eg snapTo:"R")
    if (this.snapTo) this._resolvePercentageSize(true);

    // fire up/down chain parent/child master/peer notifications 
    if (this.parentElement) this.parentElement.childResized(this, deltaX, deltaY, reason);
    if (this.masterElement) this.masterElement.peerResized(this, deltaX, deltaY, reason);

    var peers = this.peers;
    if (peers) {
        for (var i = 0; i < peers.length; i++) {
            if (isc.isA.Canvas(peers[i])) peers[i].masterResized(deltaX, deltaY, reason);
        }
    }
    
    

    //>CornerClips
    // Also checking for this._cornerClips because Canvas.init() calls resizeTo() before corner
    // clips are created.
    if (this.clipCorners && this._cornerClips) {
		var clips = this._cornerClips;
		if (clips.TR) clips.TR.moveBy(deltaX, null);
		if (clips.BL) clips.BL.moveBy(null, deltaY);
		if (clips.BR) clips.BR.moveBy(deltaX, deltaY);
    }
    //<CornerClips
    
    //>DragScrolling
    // Kill any cached drag-scrolling thresholds resolved from percentages to pixel values - 
    // these will have to be recalculated as percentage of the new viewport size. Done lazily
    // on drag scrolling in handleDropMove()
    
    if (this._hDragScrollThreshold != null) delete this._hDragScrollThreshold;
    if (this._vDragScrollThreshold != null) delete this._vDragScrollThreshold;
    //<DragScrolling
    
    this.resized(deltaX, deltaY, reason);
},

_handleResized : function () {},

//>	@method   canvas.resized()
//  Observable method called whenever a Canvas changes size. Note that if this canvas is
// +link{canvas.overflow,overflow:"visible"}, and is waiting for a queued redraw (see 
// +link{canvas.isDirty()}), the value for +link{canvas.getVisibleWidth()} and
// +link{canvas.getVisibleHeight()} will be unreliable until <code>redraw()</code> fires.
// @visibility external
//<

resized : function (deltaX, deltaY) {},

// Fired when the viewport size changes but not the overall widget size
// Used to resize children and peers with snapTo:true and percent sizing
// Also resizes widgets for which this is the percentSource - handled by observation 
innerSizeChanged : function (reason) {
    this.layoutChildren(reason);
    var peers = this.peers;
    if (peers) {
        for (var i = 0; i < peers.length; i++) {
            if (!peers[i].percentSource && peers[i].snapTo && 
                peers[i].percentBox == this._$viewport
               )
           {
               peers[i]._resolvePercentageSize();
           }
        }
    }
},

//> @method canvas.setPercentSource() [A]
// Setter method for the +link{canvas.percentSource,percentSource} attribute.
// @parameter [sourceWidget] (Canvas) New percent source (if ommitted existing
//                                      percentSource will just be cleared).
// @visibility external
// @group sizing
//<
setPercentSource : function (sourceWidget, initTime) {
    
    if (isc.isA.String(sourceWidget)) sourceWidget = window[sourceWidget];
    if (!initTime && this.percentSource == sourceWidget) return;
    
    if (this.percentSource && this.isObserving(this.percentSource, "innerSizeChanged")) {
        this.ignore(this.percentSource, "innerSizeChanged");
        this.ignore(this.percentSource, "resized");
    }
    
    if (!isc.isA.Canvas(sourceWidget)) {
        this.percentSource = null;
        return;
    }
    this.percentSource = sourceWidget;    
    this.observe(sourceWidget, "innerSizeChanged", "observer.percentSourceInnerSizeChanged()");
    this.observe(sourceWidget, "resized", "observer._resolvePercentageSize()");
},

percentSourceInnerSizeChanged : function () {
    if (this.percentBox == this._$viewport) this._resolvePercentageSize();
},


_$childResized : "childResized",
childResized : function (child, deltaX, deltaY, reason) {

    // if a child changes size, the size of our content has changed, so adjustOverflow.  For
    // example, we may need to grow/shrink to fit (overflow:visible), or show or hide scrollbars
    // (overflow:auto).
    
    
    if (this.allowContentAndChildren && this.overflow == isc.Canvas.VISIBLE)
        this._resetHandleOnAdjustOverflow = true;
    this._markForAdjustOverflow(this._$childResized);
    //this.logWarn("child resize: " + this.getStackTrace());
},

peerResized : function (peer, deltaX, deltaY, reason) { },

masterResized : function (deltaX, deltaY, reason) {    
	this._resolvePercentageSize();
},

//> @method canvas.dragResized()    (A)
// Observable function fired once at the end of a successful drag-resize operation.
// Useful for firing some action in response to resize without firing repeatedly on every
// dragMove while the user is drag-resizing the target.
//<
dragResized : function () {},


//>	@method	canvas.resizePeersBy()	(A)
//			resize any peers by the amounts specified, if we have any
//		@group	sizing
//		@param	deltaX		(number)	amount to resize horizontally (may be negative)
//		@param	deltaY		(number)	amount to resize vertically (may be negative)
//<
resizePeersBy : function (deltaX, deltaY) {
    
    var peers = this.peers;
	if (peers) {
		for (var i = 0; i < peers.length; i++) {
			if (peers[i] && peers[i]._resizeWithMaster) {
				peers[i].resizeBy(deltaX, deltaY);
            }
		}
	}
},


//>	@method	canvas.layoutChildren()	([A])
//
// <code>layoutChildren()</code> is where a Canvas should implement a sizing policy for it's
// Canvas children.  Since <code>layoutChildren</code> calls parentResized() on its children,
// +link{Canvas.parentResized} is a good place for a child to implement a layout policy that
// can be used within any parent.
// <P>
// Recommended practice for a Canvas that manages Canvas children is to create those children
// without any initial coordinate or size settings and do all sizing when layoutChildren() is
// called.
// <P>
// layoutChildren() is always called at least once before children are drawn, and is called
// automatically whenever the viewport size changes (which includes both resizing and
// introduction/removal of scrolling).  layoutChildren() can also be manually invoked in any
// other component-specific situation which changes the layout.
// <P>
// NOTE: layoutChildren() may be called before draw() if a widget is resized before draw(), so
// be sure to avoid errors such as assuming that any children you automatically create have
// already been created.
// <P>
// NOTE: auto-sizing: layoutChildren() is also called once during the initial draw(), before
// children are drawn, with a "reason" of "initial draw".  During this invocation of
// layoutChildren() it is legal to directly draw children (call child.draw()), which is
// otherwise never allowed.  This allows a Canvas to implement an auto-sizing layout policy by
// drawing some children before deciding on the sizes of remaining children, which is far more
// efficient than drawing all children and resizing some of them after they are drawn.
// @param reason (string) reason why layoutChildren() is being called, passed when framework
//                        code invokes layoutChildren()
//
//  @visibility external
//	@group	sizing
//<
layoutChildren : function (reason, deltaX, deltaY) {
    if (this.children) this._resolveChildPercentSizes();
},

// tell any percent-size children to update size
_resolveChildPercentSizes : function () {
    var children = this.children;
    if (children != null && children.length > 0) {
        for (var i = 0; i < children.length; i++) {
            if (isc.isA.Canvas(children[i])) children[i].parentResized();
        }
    }
},

//>	@method	canvas.resizeTo()   ([])
//			Resizes the widget to the specified width and height (moves the right and/ or bottom
//          sides of the widget). The width and height parameters can be expressed as a percentage
//          of viewport size or as the number of pixels.
//		@group	sizing
//		@param	[width]		(number)	new width for canvas
//		@param	[height]	(number)	new height for canvas
//      @return (boolean) whether the size actually changed
//      @visibility external
//      @example    resize
//<
// @param [animating] (boolean) optional internal param passed if this is a resize occurring as
// part of an animation
// @param [suppressHandleUpdate] (boolean) If passed avoid actually updating the handle
resizeTo : function (width, height, animating, suppressHandleUpdate) {
    if (isc._traceMarkers) arguments.__this = this;

    if (width == null && height == null) return false;

    var deltaX = this.getDelta(this._$width, width, this.getWidth()),
        deltaY = this.getDelta(this._$height, height, this.getHeight());
	// now call resizeBy to do the work for us
	return this.resizeBy(deltaX, deltaY, animating, suppressHandleUpdate);
},

//>	@method	canvas.resizeToEvent()
//		Resize according to an event, such as when resizing in a drag.
//		Uses isc.EventHandler.lastEvent for the event coordinates.
//
//		@group	sizing, events
//		@param	resizeEdge		(string)	Edge or corner to resize (eg: "T" or "BR", etc).
//<
resizeToEvent : function (resizeEdge) {
	var EH = this.ns.EH,
		event = EH.getLastEvent(),
		x =	event.x,
        y = event.y,
		left = this.getPageLeft(),
		top = this.getPageTop(),
		right = this.getPageRight(),
		bottom = this.getPageBottom();

        // Snap-to-grid - adjust x/y to grid as required, before validity checks 
        
        var snapChild = EH.getDragTarget(event);
        var snapParent = EH.getDragTarget(event).parentElement;
        
        if (snapParent) {
            if (snapChild.snapResizeToGrid == true || 
                (snapChild.snapResizeToGrid == null && snapChild.snapToGrid == true) ||
                (snapChild.snapResizeToGrid == null && 
                    (snapParent.childrenSnapResizeToGrid == true ||
                       (snapParent.childrenSnapResizeToGrid == null &&
                        snapParent.childrenSnapToGrid == true)))) {
                
                if (snapParent.snapAxis == isc.Canvas.HORIZONTAL ||    
                    snapParent.snapAxis == isc.Canvas.BOTH) {                        
                    var snapParentContentOffset = 
                        (snapParent.getPageLeft() + snapParent.getLeftBorderSize() +
                          snapParent.getLeftMargin() - snapParent.getScrollLeft()); 
                    x -= snapParentContentOffset; 
                    x = snapParent.getHSnapPosition(x) + snapParent.getHSnapOrigin(snapChild);
                    x += snapParentContentOffset; 
                }
                if (snapParent.snapAxis == isc.Canvas.VERTICAL ||    
                    snapParent.snapAxis == isc.Canvas.BOTH) {
                    snapParentContentOffset = 
                        (snapParent.getPageTop() + snapParent.getTopBorderSize() +
                          snapParent.getTopMargin() - snapParent.getScrollTop()); 
                    y -= snapParentContentOffset; 
                    y = snapParent.getVSnapPosition(y) + snapParent.getVSnapOrigin(snapChild);
                    y += snapParentContentOffset; 
                }
            }
        }
        
    //>DEBUG
    if (this.logIsDebugEnabled("dragResize")) {
        this.logDebug("resizeToEvent: coords: " + 
                      isc.Log.echo({x:x, y:y, left:left, top:top, right:right, bottom:bottom}),
                      "dragResize");
    } //<DEBUG

	resizeEdge = resizeEdge || EH.resizeEdge || "BR";

    // for each side participating in the resize, figure out how much it should change,
    // refusing to resize beyond the min/max height and width.
    
    // top or bottom
	if (resizeEdge.contains("T")) {
		var height = Math.min(this.maxHeight, Math.max(bottom - y, this.minHeight));
		top = bottom - height;
	} else if (resizeEdge.contains("B")) {
		var height = Math.min(this.maxHeight, Math.max(y - top, this.minHeight));
		bottom = top + height;
	}
	
    // left or right
	if (resizeEdge.contains("L")) {
		var width = Math.min(this.maxWidth, Math.max(right - x, this.minWidth));
		left = right - width;
	} else if (resizeEdge.contains("R")) {
		var width = Math.min(this.maxWidth, Math.max(x - left, this.minWidth));
		right = left + width;
	}
	
    var newWidth = right - left,
        newHeight = bottom - top;
    
    this.setPageRect(left, top, newWidth, newHeight, true);

	// set EH.dragResizeWidth and EH.dragResizeHeight
	//	so other routines can know how big the resizing thing will be
	EH.dragResizeWidth = newWidth;
	EH.dragResizeHeight = newHeight;
	
	// HACK: if we're resizing the dragTracker, redraw immediately, this looks MUCH cleaner
	if (this == this.ns.EH.dragTracker) this.redrawIfDirty();
},

// Generic interaction for resizing some target widget as we are moved
// ---------------------------------------------------------------------------------------

resizeTarget : function (target, vertical, realTime, offset, ignore, coord, targetAfter) {
    // ignore: number of pixels between targetCoord and coord to ignore for the purposes of
    // this calculation (used for skipping intervening collapsed headers in SectionStack)
    ignore = ignore || 0;
    // offset: drag offset
    offset = offset || 0;

    if (coord == null) coord = vertical ? isc.EH.getY() : isc.EH.getX();
    coord += offset;
    
    // don't allow to drag the target past the edge of the parent
    // Essentially what we're doing here is keeping the drag-widget (EG splitBar) inside
    // the parent rect
    if (this.parentElement) {
        var parentRect = this.getParentPageRect(),
            maxCoord = vertical ? (parentRect[1] + parentRect[3]) 
                                : (parentRect[0] + parentRect[2]);
        maxCoord -= vertical ? this.getVisibleHeight() : this.getVisibleWidth();
        if (coord > maxCoord) coord = maxCoord;
    }
    
    targetAfter = targetAfter != null ? targetAfter : !vertical && this.isRTL();

    var min = vertical ? target.getMinHeight() : target.getMinWidth(),
        max = vertical ? target.getMaxHeight() : target.getMaxWidth();
        
    var targetCoord;
    if (targetAfter) {
        targetCoord = (vertical ? target.getPageBottom() : target.getPageRight())
            // adjust by the resizeBar's thickness since newSize is determined by 
            // the right coordinate of the target as compared to the right coordinate of the
            // resizeBar
            - (vertical ? this.getVisibleHeight() : this.getVisibleWidth());
    } else {
        targetCoord = vertical ? target.getPageTop() : target.getPageLeft();
    }


    // determine size implied by the resizeBar's current position
    var newSize = !targetAfter ? 
                            // target before us: target is to our left (or top), 
                            // newSize is our coord - target's left
                            coord - targetCoord - ignore :
                            // target after us: target is to our right (or bottom),
                            // newSize is target's right - our coord
                            targetCoord - coord - ignore;
    

    // clamp size to max min
    if (newSize < min) {
        newSize = min;
    } else if (newSize > max) {
        newSize = max;
    }
    // save off targetSize for finishTargetResize()
    this._targetSize = newSize;

    // calculate where the resizeBar should be 
    coord = targetCoord + ignore + (targetAfter ? - newSize : newSize);

    if (realTime) {
        // resize the target
        vertical ? target.setHeight(this._targetSize) : target.setWidth(this._targetSize);
    } else {
        // just move this widget
        vertical ? this.setPageTop(coord) : this.setPageLeft(coord);
    }
},

finishTargetResize : function (target, vertical, realTime) {
    if (realTime) return;
    vertical ? target.setHeight(this._targetSize) : target.setWidth(this._targetSize);
},

// ---------------------------------------------------------------------------------------

//> @method canvas.parentResized() 
// Fires when the interior size of the parent changes, including parent resize and
// scrollbar introduction or removal.
// <p>
// This method allows a child to implement a layout policy that can be used within any 
// parent, such as a Resizer component that always snaps to the parent's
// bottom-right corner.  The default implementation of this method applies a child's
// percent sizes, if any, or implements layout based on the +link{Canvas.snapTo} property
// @group sizing
// @visibility external
//<
parentResized : function () {
    if (isc._traceMarkers) arguments.__this = this;
    this._resolvePercentageSize();
},

// called on an individual child to tell it to resolve it's own percent sizes and/or snapTo
// coordinates.
_resolvePercentageSize : function (positionOnly) {
    // percentBox:"custom" -- assume the percentage sizing / positioning will be explicitly
    // managed by some custom logic
    if (this.snapTo != null && this.percentBox != "custom") {
        // if the child has percent size, need to resize first so that centering logic is correct
        if ((this._percent_width || this._percent_height) && !positionOnly) {
            this.resizeTo(this._percent_width, this._percent_height);
        }
        var target, targetOrigin, insideCoords;
        target = (this.masterElement ? this.masterElement : this.parentElement);
        if (!target) return; // use this info later to implement snapTo page
        
        isc.Canvas.snapToEdge(target, this.snapTo, this, this.snapEdge);
        
    }
    // if snapTo was invalid or really is null
    if (this.snapTo == null && !positionOnly) {
        if (this._percent_left || this._percent_top || 
            this._percent_width || this._percent_height)
        {
            this.setRect(this._percent_left, this._percent_top,
                         this._percent_width, this._percent_height);
        }
    }
},
    
prepareForDragging : function () {
    var EH = this.ns.EH;
    // this would indicate that a child has set itself as the dragTarget, and then
    // prepareForDragging bubbled to this Canvas.  By default, we leave this alone.  
    if (EH.dragTarget) return;
    
    // NOTE: interesting case:
    // - a parent that wants to be drag resizable may have children which are flush with the
    //   parent's edges.  If those children are themselves resizable they will have set
    //   themselves as the dragTarget.  The parent may want to override this.

	// if the target can be resized by dragging, 
	if (this.canDragResize) {
		// see if the cursor is over an edge where this Canvas can be resized
		EH.resizeEdge = this.getEventEdge();

		if (EH.resizeEdge) {
			// built-in drag resizing:
            // - EventHandler will automatically show a resize animation according to
            //   this.dragAppearance, and will permanently resize this Canvas on mouseUp
            // - this Canvas will receive dragResizeStart/Move/Stop events, which bubble to
            //   parents
			EH.dragTarget = this;
			EH.dragOperation = EH.DRAG_RESIZE;
			EH.dragMoveAction = EH._resizeDragMoveTarget;
		}
	}
	if (!EH.dragTarget) { // not a drag resize..
		if (this.canDragReposition) {
            // built-in drag repositioning
			// - EventHandler will automatically show a move animation according to
            //   this.dragAppearance, and will permanently reposition this Canvas on mouseUp
            // - this Canvas will receive dragRepositionStart/Move/Stop events, which bubble to
            //   parents
			EH.dragTarget = this;
			EH.dragOperation = EH.DRAG_REPOSITION;
			EH.dragMoveAction = EH._moveDragMoveTarget;
		
		} else if (this.canDrag) {
			// generic drag interaction:
            // - EventHandler will show a move animation according to this.dragAppearance
            // - this Canvas will receive dragStart/Move/Stop events, which bubble to parents
			EH.dragTarget = this;
			EH.dragOperation = EH.DRAG;
		}
	}

    // not draggable (all 3 flags false: canDrag, canDragResize, canDragReposition), so don't
    // set a dragTarget.  NOTE: allow this event to bubble, so parents can override our drag
    // settings.
},

//> @method  Canvas.setDragTracker()     
// If +link{canvas.dragAppearance} is set to <code>"tracker"</code>, this method will be called
// (if defined), when the user starts to drag this widget. It is an opportunity to update the
// drag tracker to display something relative to this canvas.  Typical implementation will
// be to call +link{EventHandler.setDragTracker()}, passing in the desired custom tracker HTML 
// as a string
// @return  (boolean) Return false to suppress bubbling, and prevent <code>setDragTracker()</code>
//                      from being called on this widget's ancestors.
// @group dragdrop
// @visibility external
// @example dragTracker
//<

// ------------------
// No Drop indicator
// If a widget expressly disallows drop in some cases, we want to indicate this with a
// no-drop cursor.
// Example use case: Disallowing drop in certain tree-grid nodes.

//> @method Canvas.setNoDropIndicator()
// Display a "not-allowed" cursor when the user drags over this canvas.
// If +link{Canvas.shouldSetNoDropTracker} is <code>true</code> will also replace the current
// drag tracker (if visible) with the +link{Canvas.noDropTracker} image.
// @see Canvas.clearNoDropIndicator()
// @see Canvas.shouldSetNoDropTracker
//<
setNoDropIndicator : function () {
     
    this._noDropIndicatorSet = true;
    
    // The actual 'not allowed cursor' will be picked up by getCurrentCursor()
    // This way we don't have to remember the previous cursor and reset to it on 
    // clearNoDropIndicator()
    this._updateCursor();

    // If we should show the no-drop tracker image, and the drag-tracker is showing, do this
    // now.
    
    if (this.shouldSetNoDropTracker && isc.EH.dragTracker && isc.EH.dragTracker.isVisible()) {
        // Remember the current dragTracker content so we can clear if need be
        
        if (!this._activeDragTracker) this._activeDragTracker = isc.EH.dragTracker.getContents();
        isc.EH.setDragTracker(this.imgHTML(this.noDropTracker));
    }
},

//>@method Canvas.clearNoDropIndicator()
// Stop displaying the "not-allowed" cursor (and special no-drop tracker if appropriate)
// while the user drags over this canvas.
// @see Canvas.setNoDropIndicator()
//<
clearNoDropIndicator : function () {
    
    if (!this._noDropIndicatorSet) return;
    delete this._noDropIndicatorSet;
    this._updateCursor();
    
    if (this.shouldSetNoDropTracker && isc.EH.dragTracker) {
        isc.EH.setDragTracker(this._activeDragTracker);
        delete this._activeDragTracker;
    }
},

//> @attr canvas.shouldSetNoDropTracker (boolean : varies by browser : [IRWA])
// When +link{Canvas.setNoDropIndicator()} is called, should we replace the current drag-tracker
// with the +link{canvas.noDropTracker} image?<br>
// By default this property is set to true in Opera only as Safari, Moz and IE all support a native 
// <code>"not-allowed"</code> cursor.
// @see canvas.setNoDropIndicator()
//<
// Unsupported last tested on Opera version 9.27
shouldSetNoDropTracker : isc.Browser.isOpera,

//> @attr   Canvas.noDropTracker    (SCImgURL : "[SKIN]/shared/no_drop.png" : [IRWA])
// Image to display as the 'no-drop' drag tracker when +link{shouldSetNoDropTracker} is true
// @see canvas.shouldSetNoDropTracker
//<
noDropTracker:"[SKIN]/shared/no_drop.png",


//>DragScrolling
// When a user is dragging a dragTarget widget over a scrollable widget which will accept drop
// we automatically scroll the canAcceptDrop widget when the mousepointer is close to the
// edge of the viewport.
// Can be disabled by setting 'canDragScroll' to false.

//>	@method	canvas.shouldDragScroll() [A]
// If this widget is showing scrollbars, and a user drags close to the edge of the viewport,
// should we scroll the viewport in the appropriate direction?
// Returns this.canDragScroll by default.
// @group events
// @group dragging
// @visibility external
//<
shouldDragScroll : function () {
    return this.canDragScroll;
},

// Determine whether the last event occurred in either the top or bottom scroll thresholds
// Returns -1 if the event occurred in the top scroll threshold, so we should scroll up, and
// +1 if the event occurred in the bottom threshold, so we should scroll down (or zero if not
// over either threshold).
_getVDragScrollDirection : function (offsetY) {

    // resolve the max/min scroll increments (typically specified as percentages)
    // to numeric values        
    var vDragThreshold = this.getVDragScrollThreshold();
        
    if (offsetY < vDragThreshold) return -1;
    if (offsetY > (this.getViewportHeight() - vDragThreshold)) return 1;
    return 0;
},

// Determine whether the last event occurred in either the left or right scroll thresholds
_getHDragScrollDirection : function (offsetX) {
    var hDragThreshold = this.getHDragScrollThreshold();
        
    if (offsetX < hDragThreshold) return -1;
    if (offsetX > (this.getViewportWidth() - hDragThreshold)) return 1;
    return 0;
},

// Determine whether the last event occurred over any drag scroll threshold
_overDragThreshold : function (direction) {
    var offsetY = (this.getOffsetY() - this.getScrollTop()),
        offsetX = (this.getOffsetX() - this.getScrollLeft());
        
    if (direction != null) {
        if (direction == isc.Canvas.VERTICAL) 
            return this._getVDragScrollDirection(offsetY) != 0;
        else
            return this._getHDragScrollDirection(offsetX) != 0;
    }
        
    return (this._getVDragScrollDirection(offsetY) != 0 || 
            this._getHDragScrollDirection(offsetX) != 0);
},

// getHDragScrollThreshold() / getVDragScrollThreshold() - method to determine the size of the
// dragScrollThreshold on either axis.
// By default will return this.dragScrollThreshold, resolved from a percentage size if 
// necessary.
getHDragScrollThreshold : function () {
    // We cache the values to avoid recalculating from percentage size on every mouseMove.
    // These cached values are dropped on widget resize.
    if (this._hDragScrollThreshold != null) return this._hDragScrollThreshold;
    var tH = this.dragScrollThreshold;
    if (isc.isA.Number(tH)) this._hDragScrollThreshold = tH;
    else {
        // assume it's a percentage
        tH = parseInt(tH);
        if (!isNaN(tH)) {
            this._hDragScrollThreshold = parseInt(tH * this.getViewportWidth() / 100);
            return this._hDragScrollThreshold;
        } else {
            //>DEBUG
            isc.Log.logWarn("Unable to resolve specified drag scroll threshold '" +
                            this.dragScrollThreshold + "' to a valid size. Should be specified as" +
                            " an absolute pixel value, or a percentage of widget viewport.");
            //<DEBUG
            return 0;
        }
    }
},
getVDragScrollThreshold : function () {
    if (this._vDragScrollThreshold != null) return this._vDragScrollThreshold;
    var tH = this.dragScrollThreshold;
    if (isc.isA.Number(tH)) this._vDragScrollThreshold = tH;
    else {
        // assume it's a percentage
        tH = parseInt(tH);
        if (!isNaN(tH)) {
            this._vDragScrollThreshold = parseInt(tH * this.getViewportHeight() / 100);
            return this._vDragScrollThreshold;
        } else {
            //>DEBUG
            isc.Log.logWarn("Unable to resolve specified drag scroll threshold '" +
                            this.dragScrollThreshold + "' to a valid size. Should be specified as" +
                            " an absolute pixel value, or a percentage of widget viewport.");
            //<DEBUG
            return 0;
        }
    }
},

// setupDragScroll
// - If the user is drag-hovering close to the ends of the widget, setup a timer event to start
//   scrolling in the appropriate direction.
_setupDragScroll : function (direction) {

    // If we're already waiting to scroll no-op
    if (this._dragScrollTimer != null) return;
    
    var offsetY = (this.getOffsetY() - this.getScrollTop()),
        offsetX = (this.getOffsetX() - this.getScrollLeft()),
        horizontal = this._getHDragScrollDirection(offsetX),
        vertical = this._getVDragScrollDirection(offsetY);
        
    this._dragScrollTimer =
        isc.Timer.setTimeout({target:this, methodName:"_performDragScroll",
                              args:[horizontal,vertical,true, direction]}, this.dragScrollDelay);
},

// performDragScroll
// Actually scroll the widget in the appropriate direction in response to the user
// drag-hovering close to the edge of the viewport.
// This method is always fired in response to a timer event, set up from either:
// - _setupDragScroll() called by event handler code when the user is dragging over the edge
//   of this widget. In this case we're passed an intended direction of scroll, and the 
//   boolean 'firstScroll' parameter. We use these params to avoid the possibility of the 
//   user hovering on one side of the widget long enough to start the drag-scroll timer, then 
//   moving to a different side, and having scrolling beging before the user has drag-hovered 
//   for the requisite length of time on that other side.
// or:
// - _performDragScroll() will setup a timer to call itself, in order to continuously scroll
//  as long as the user hovers over a scroll threshold on the widget.
_performDragScroll : function (horizontal, vertical, firstScroll, direction) {

    this._dragScrollTimer = null;

    var hScrollIncrement = 0, vScrollIncrement = 0;
    if (this.ns.EH.dragging && this.containsEvent()) {
        
        var offsetX = this.getOffsetX() - this.getScrollLeft(),
            offsetY = this.getOffsetY() - this.getScrollTop(),
            viewportWidth = this.getViewportWidth(),
            viewportHeight = this.getViewportHeight();
            
        // the scroll increments may have been set up as percentage values.
        // If so resolve these to pixel values, and cache them for the next _performDragScroll() 
        // call.
        // Note that if the user has moved the mouse outside the scroll area when this method fires
        // we'll clear these cached values, so they should never become out of date due to the
        // widget's scrollWidth, etc. changing.
        if (!isc.isA.Number(this.maxDragScrollIncrement)) {
            // resolve percentages
            var maxInc = parseInt(this.maxDragScrollIncrement);
            if (!isc.isA.Number(maxInc)) 
                this.logWarn("Unable to resolve this.maxDragScrollIncrement '" + 
                             this.maxDragScrollIncrement + "' to a valid value. This should be an " +
                             "absolute pixel value or a percentage to scroll by.");
            
            // cache for repeated scroll events
            this._maxHInc = parseInt(maxInc / 100 * this.getScrollWidth());
            this._maxVInc = parseInt(maxInc / 100 * this.getScrollHeight());
        } else {
            this._maxHInc = this._maxVInc = this.maxDragScrollIncrement;
        }
        
        if (!isc.isA.Number(this.minDragScrollIncrement)) {
            // resolve percentages
            var minInc = parseInt(this.minDragScrollIncrement);
            if (!isc.isA.Number(minInc)) 
                this.logWarn("Unable to resolve this.minDragScrollIncrement '" + 
                             this.minDragScrollIncrement + "' to a valid value. This should be an " +
                             "absolute pixel value or a percentage to scroll by.");
            
            // cache for repeated scroll events
            this._minHInc = parseInt(minInc / 100 * (this.getScrollWidth()-viewportWidth));
            this._minVInc = parseInt(minInc / 100 * (this.getScrollHeight()-viewportHeight));
        } else {
            this._minHInc = this._minVInc = this.minDragScrollIncrement;
        }
        
        // Direction param - certain widgets only cause drag-scrolling either vertically 
        // or horizontally. Derived by EH from canvas.dragScrollDirection, and passed into
        // this method as the direction param.
        // If we are passed a 'direction' parameter, only allow drag-scrolling in the
        // direction specified.
        var hDSDir = (direction == isc.Canvas.VERTICAL ? 0 : this._getHDragScrollDirection(offsetX)),
            vDSDir = (direction == isc.Canvas.HORIZONTAL ? 0 : this._getVDragScrollDirection(offsetY));

        // This event is fired on a timer.
        // We want to avoid scrolling in the case where a user passes over the scroll threshold 
        // on one axis (which kicks off the timer), and is positioned over a different scroll 
        // threshold when the timer executes, as we want to scroll only if the user has 
        // consciously hovered over a scroll threshold.
        // Once we've kicked off the first scroll, we don't need to be strict about this -
        // if the user moves their mouse to a different scroll area while scrolling is in
        // progress we can assume it's a conscious attempt to scroll in another direction.            
        if (firstScroll) {
            // if the direction has changed, set to zero - this will prevent scrolling from
            // occurring
            if (horizontal != 0 && horizontal != hDSDir) 
                horizontal = 0;
            if (vertical != 0 && vertical != vDSDir)
                vertical = 0;
        } else {
            horizontal = hDSDir;
            vertical = vDSDir;
        }
                
        hScrollIncrement = this.getScrollIncrement(horizontal,
                                                        offsetX,
                                                        viewportWidth,
                                                        this.getHDragScrollThreshold(), 
                                                        this._maxHInc, 
                                                        this._minHInc);
        vScrollIncrement = this.getScrollIncrement(vertical, 
                                                        offsetY, 
                                                        viewportHeight, 
                                                        this.getVDragScrollThreshold(),
                                                        this._maxVInc, 
                                                        this._minVInc);

        // Don't bother scrolling / setting up repeating scrolls if we're already at the end
        if ((hScrollIncrement > 0 && (this.getScrollLeft() >= this.getScrollRight())) ||
            (hScrollIncrement < 0 && (this.getScrollLeft() <= 0))) hScrollIncrement = 0;
        if ((vScrollIncrement > 0 && (this.getScrollTop() >= this.getScrollBottom())) ||
            (vScrollIncrement < 0 && (this.getScrollTop() <= 0))) vScrollIncrement = 0;        
    }
    
    if (hScrollIncrement != 0 || vScrollIncrement != 0) {
        this.scrollBy(hScrollIncrement, vScrollIncrement);
        // continue to scroll.  We do this on a timeout, rather than re-calling this method
        // directly to allow normal event processing to continue.
        this._dragScrollTimer = isc.Timer.setTimeout(
                                    {target:this, 
                                     methodName:"_performDragScroll", 
                                     args:[null,null,null,direction]}, 50
                                );
        
    // The mouse has moved out of the scrollable area since we last started the timer, or
    // we've reached the edge of the widget.                                   
    } else {
        // clear out the cached scroll increments - we'll lazily recalculate when drag
        // scrolling begins again.
        delete this._maxHInc;
        delete this._minHInc;
        delete this._maxVInc;
        delete this._minVInc;
    }       
},

// Internal method to determine how much to scroll by when drag-scrolling this widget, based 
// on mouse position [abstracted out to work vertically or horizontally]
// For drag scrolling to occur, the mouse must be positioned <= 1 * this.dragScrollThreshold
// from the edge of the viewport.
// The closer the mouse is to the edge of the viewport, the faster the widget will scroll
// - this is controlled by the 'maxDragScrollIncrement' / 'minDragScrollIncrement' properties
//   when the user is hovering right over the edge of the window, the window will scroll by the
//   maximum value, when hovering exactly 1* the threshold from the edge, it will scroll by the
//   minimum value.
// Return zero if the widget should not scroll.
getScrollIncrement : function (direction, eventOffset, viewportSize, threshold, maxInc, minInc) {
    if (direction == null || direction == 0) return 0;

    // Resolve the offset to the distance from the start of the scroll threshold
    if (direction > 0) {
        eventOffset = eventOffset - (viewportSize - threshold);
    } else if (direction < 0) {
        eventOffset = threshold - eventOffset;
    }

    // Don't scroll if we're outside the threshold area (or outside the widget)
    if (eventOffset < 0 || eventOffset > threshold) return 0;

    // Determine the amount to scroll based on the max/min scroll increments, and how close
    // we are to the edge of the widget.
    var increment = direction *
            (
                (eventOffset / threshold) *  (maxInc - minInc)
                + minInc
            );
    return parseInt(increment);
},
//<DragScrolling

// Overflow handling
// --------------------------------------------------------------------------------------------
// Managing what happens when contents overflow the Canvas' specified size: expanding, clipping,
// scrolling, etc


hasInherentHeight : function () {
    if (this.inherentHeight != null) return this.inherentHeight;
    return (this.children == null && 
            (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H));
},

hasInherentWidth : function () {
    if (this.inherentWidth != null) return this.inherentWidth;
    return (this.children == null && 
            (this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V));
},

canOverflowWidth : function () {
    return this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_H;
},

canOverflowHeight : function () {
    return this.overflow == isc.Canvas.VISIBLE || this.overflow == isc.Canvas.CLIP_V;
},

//>	@method	canvas.getOverflow()
//		Return the overflow of a Canvas.
//		@group	positioning, sizing
//		
//<
getOverflow : function () {
    return this.overflow;
},


//>	@method	canvas.setOverflow()    ([A])
// Update the +link{Canvas.overflow, overflow} of a Canvas after it has been created.
//		@group	positioning, sizing
//		@param	newOverflow	(Overflow)		New overflow value.
// @visibility external
//<
setOverflow : function (newOverflow) {

    //>Animation
    // Finish any hide/show animations that are running
    // Required since we force overflow to hidden during animation, then reset to original 
    // overflow when animation completes.
    if (this._animatingHide != null && !this._hidingAsAnimation) 
        this.finishAnimation(this._animatingHide);
    if (this._animatingShow != null && !this._showingAsAnimation) 
        this.finishAnimation(this._animatingShow);
    //<Animation

    if (this.overflow == newOverflow) return; // no-op

    var oldOverflow = this.overflow;
	this.overflow = newOverflow;

    // If we're drawn, we need to update the elements width, height, overflow, and
    // clip so that they reflect the new overflow state.  Essentially, we make the setting
    // match the initial values as set in getTagStart() and then call 'adjustOverflow' to do
    // standard adjustments.
    
    if (!this.isDrawn()) return;

    // If we WERE showing custom scrollbars, and the overflow is no longer scroll or auto,
    // clear them out
    if (newOverflow != isc.Canvas.SCROLL && newOverflow != isc.Canvas.AUTO &&
         (this.hscrollOn || this.vscrollOn)) 
    {
        // clear flags (note flag may be set with no scrollbar having been created)
        this.hscrollOn = this.vscrollOn = false;
        // hide custom scrollbars if they exist
        // (Note: we could destroy the scrollbars here, but it's not necessary - this is 
        // quicker, and this way the scrollbars may be re-used if the overflow is changed
        // back to auto / scroll.  If the widget is clear()'d / destroy()'d, the scrollbars
        // will get cleaned up.)
        if (this.hscrollbar != null) this.hscrollbar.hide();
        if (this.vscrollbar != null) this.vscrollbar.hide();
    }

    
    if (isc.Browser.isIE && 
        (newOverflow == isc.Canvas.CLIP_H || newOverflow == isc.Canvas.CLIP_V)) 
    {    
        this.markForRedraw();
        return;
    }
        
    var handle = this.getStyleHandle();

    // set the overflow property on the handle
	handle.overflow = this._getHandleOverflow();
    // Set the initial width / height of the handle
    var sizeArray = this._getInitialHandleSize();
    handle.width = sizeArray[0];
    handle.height = sizeArray[1];

    

    // If we're not using clip-scrolling, we want to clear out any clip that's already been
    // set on the widget's handle.
    // (_adjustOverflow() will apply a clip to the handle in some cases, but will not remove
    // an existing clip)                       
    
    if (handle.clip != null && handle.clip != "" && 
        handle.clip != "rect(auto auto auto auto)") 
    {
        handle.clip = (isc.Browser.isIE ? "rect(auto)" : "");
    }

    // The handle will now have it's overflow, size and clip set to the values they would
    // have at the end of getTagStart().
    // Call adjustOverflow to handle resizing to accomodate contents, setting additional
    // clips, showing scrollbars, etc.
	this.adjustOverflow("setOverflow");
    
    
    if (oldOverflow == isc.Canvas.VISIBLE && newOverflow != isc.Canvas.VISIBLE) {
        var dX = Math.max(this.getScrollWidth() - this.getInnerWidth(), 0),
            dY = Math.max(this.getScrollHeight() - this.getInnerHeight(), 0);
        // Note the handle in this case is shrinking by the difference between scroll size
        // and available space, so pass in -dX/-dY
        if (dX > 0 || dY > 0) this._resized(-dX, -dY, "overflow changed");

    } else if (oldOverflow != isc.Canvas.VISIBLE && newOverflow == isc.Canvas.VISIBLE) {
        var dX = Math.max(this.getScrollWidth() - this.getInnerWidth(), 0),
            dY = Math.max(this.getScrollHeight() - this.getInnerHeight(), 0);
        if (dX > 0 || dY > 0) this._resized(dX, dY, "overflow changed");
    }
    
    
    
    // Note: since the dynamic _canFocus() method defaults to depending on this.overflow
    // being hidden, we should call the _updateCanFocus() method here to update the native
    // handle focus and tab-index behavior.
    // NOTE: avoid doing this in common cases where it's not needed, since it can cause some
    // components to redraw (eg Buttons and Labels, when animating)
    if ((newOverflow == isc.Canvas.HIDDEN || newOverflow == isc.Canvas.VISIBLE) &&
        (oldOverflow == isc.Canvas.HIDDEN || oldOverflow == isc.Canvas.VISIBLE)) {
    } else {
        this._updateCanFocus();
    }
},

// set a timer to call adjustOverflow (unless one is already set).  This is to avoid redundant
// timers being set when parents set a timer to adjustOverflow on child move and resize, which
// can happen in large batches (particularly with layouts).  It might even be worthwhile to
// centralize delayed adjustOverflow() calls for all widgets into a single queue, similar to
// the redraw queue.
_markForAdjustOverflow : function (reason) {
    if (!this.isDrawn() || this.isDirty() || this.destroying || this._clearing) return;

    if (!this._overflowQueued) {
        //>DEBUG
        if (this.logIsDebugEnabled()) 
            this.logDebug("delaying adjustOverflow: " + (reason ? reason : this.getStackTrace()));
        //<DEBUG
        var theCanvas = this;
        this._overflowTimer = 
            isc.Timer.setTimeout(function () {
                if (!theCanvas.destroyed) theCanvas.adjustOverflow(reason, true)
            }, 0);
    }
    this._overflowQueued = true;
},

//> @method canvas.adjustForContent() [A]
// This method tells a component to adjust for size changes made to content by external code.
// <P>
// This is for very advanced use in which the contents of a Canvas are being directly updated by
// Ajax techniques, which is required for integration with some third-party libraries.
// Calling this method is required because browsers do not provide consistent events by which
// SmartClient can be notified of external content changes.
// <P>
// Only contents supplied to a widget via +link{canvas.contents} or via an override of
// +link{canvas.getInnerHTML()} should be manipulated directly.  Contents automatically
// generated by SmartClient components (such as the basic structure of a Button) should never be
// manipulated: these structures are considered internal, differ by platform, and will change
// without notice.
// @param immediate (boolean) 
//  By default the adjustment will occur on a small delay for performance reasons. 
//  Pass in this parameter to force immediate adjustment.
// @group sizing
// @visibility external
//<                  
adjustForContent : function (immediate) {
    var reason = "adjustForContent() called";
    if (immediate) this.adjustOverflow(reason);
    else this._markForAdjustOverflow(reason);
},


_browserDoneDrawing : function () {
    var handle = this.getHandle();

    
    if (isc.Browser.isOpera) {
        var handle = this.getHandle();
        return !(handle.scrollHeight == 0 && handle.scrollWidth == 0);
    }

    if (!isc.Browser.isIE) {
        
        //
        // clipHandle can be null in canvas.start/end mode if we're doc.write()ing
        var clipHandle = this.getClipHandle();
         if (clipHandle == null) return false;

        var scrollHeight = clipHandle.scrollHeight;
        if (scrollHeight == null || scrollHeight == 0) scrollHeight == this.getClipHandle().offsetHeight;
        
        return scrollHeight != 0;
    }

    // IE-only (including MacIE)

    
    var browserDoneDrawing;
    if (isc.Browser.isWin) {
        
        
        return handle != null && handle.scrollHeight != this._$undefined &&
                    handle.scrollHeight != 0;              
    }
},

// Flag to suppress adjustOverflow from running while we're waiting on a redraw
adjustOverflowWhileDirty:true,

//>	@method	canvas.adjustOverflow()	(A)
// Adjust the size, clipping and/or scrolling of a canvas to account for its drawn size and
// the overflow setting.
//
//		@group	sizing
//
//		@return	(boolean)	true == we're done adjusting the overflow
//							false == couldn't adjust for some reason, had to defer and call again.
//<
// NOTE: children must adjust for overflow before parents, because some parents take children's
// sizes into account when sizing themselves.
adjustOverflow : function (reason, delayed, fromRedraw) {
    if (isc._traceMarkers) arguments.__this = this;

    if (delayed && !this._overflowQueued) {
        // only one timer can be outstanding at once, but this case still happens if
        // adjustOverflow is called by other code during the timer delay (eg redraw)
        //this.logWarn("aborting unnecessary delayed adjust");
        return;
    }
    this._overflowQueued = false;


    // if not drawn yet or we're not handling overflow for this object, 
    //	just return true since we don't need to do anything
    if (!this.isDrawn() || this.overflow == isc.Canvas.IGNORE) return true;
    
    // Flag can be set to avoid all adjustOverflows while waiting on a redraw
    // Note: if we're being redrawn in response to the redraw of a dirty parent, their
    // __dirty flag will not yet have been cleared (so this.isDirty() will return true).
    // We catch that case with the explicit 3rd param
    
    if (!this.adjustOverflowWhileDirty && !fromRedraw && this.isDirty() && 
        (this.overflow != isc.Canvas.VISIBLE)) 
    {
        return;
    }    
    
    
    if (!isc.Page.isLoaded() &&
        (isc.Browser.isSafari || 
         (isc.Browser.isMoz && isc.Browser.geckoVersion < 20040616)))
    {
        // defer the 'adjustOverflow' until the page has loaded.
        isc.Page.setEvent("load", this, isc.Page.FIRE_ONCE, "_adjustOverflowForPageLoad");
            
        
        if (isc.Browser.isMoz) return;
    }
    
    // If we've set the internal flag to suppress adjustOverflow just return
    if (this._suppressAdjustOverflow) return;

    // adjust now if size is available
    if (this._browserDoneDrawing()) return this._adjustOverflow(reason);
    
    if (this.logIsDebugEnabled("overflow")) {
        this.logDebug("browser not done drawing, deferring overflow.", "overflow");
        if (this.useClipDiv) {
            this.logDebug("clipHandle sizes: " + this.echoElementSize(this.getClipHandle()),
                          "overflow");
        } 
        this.logDebug("handle sizes: " + this.echoElementSize(this.getHandle()),
                      "overflow");
    }

    // if size can't be determined, set a relatively immediate timer to check again.
    // If it still can't be determined, keep checking at a less frequent interval.
    if (!this._delayedAdjustOverflow) {
        this._markForAdjustOverflow();
        this._delayedAdjustOverflow = true;     // this flag cleared out in _adjustOverflow
    } else {
        //>DEBUG
        this.logDebug("still waiting for size to become available", "overflow"); //<DEBUG
        this._queueForDelayedAdjustOverflow();
    }   
    // return false that we didn't finish the adjustment yet
    return false;
},


_adjustOverflowForPageLoad : function () {
    if (!this.destroyed && this.isDrawn()) this.adjustOverflow("pageLoad");
},

_queueForDelayedAdjustOverflow : function() {
    isc.Canvas._queueForDelayedAdjustOverflow(this.getID());
},


// (internal) routine to implement the Canvas.overflow property, by clipping, scrolling or
// automatically expanding based on the size of content and children
_adjustOverflow : function (reason) {
    if (this._inAdjustOverflow) {
        
        return
    }
    this._inAdjustOverflow = true;
    this.__adjustOverflow(reason);
    this._inAdjustOverflow = false;
},

_supportedOverflows:{hidden:true, visible:true, scroll:true, auto:true, "clip-v":true, "clip-h":true, ignore:true},
_$sizing : "sizing",
_$overflow : "overflow",
__adjustOverflow : function (reason) {
    if (!this._supportedOverflows[this.overflow]) {
        this.logWarn("This widget has overflow specified as " + this.echo(this.overflow) + 
                     ".  This overflow setting is not supported - defaulting to overflow:\"visible\".");
        this.overflow = isc.Canvas.VISIBLE;
    }
    
    // Note: scrollHeight / scrollWidth and cacheing:
    // in getScrollHeight() / getScrollWidth(), we iterate through all our DOM children to
    // calculate a reliable scrollHeight / width.
    // This is quite expensive, so we cache the value after calculating it.
    //
    // This method (_adjustOverflow) is called by all the methods that could end up effecting
    // the scrollHeight / width of a widget (setContents, resize, addChildren...)
    // Therefore in this case we want ensure we calculate new scrollHeight / scrollWidth values.
    // 
    // getScrollHeight() / getScrollWidth() takes a parameter 'calculateNewValue' which, if true, 
    // will force the value to be recalculated rather than returning the cached value.
    // We must ensure that at the end of this method that getScrollHeight() and
    // getScrollWidth() will report accurate values.
    //
    // Start by invalidating any existing cached scrollHeight / scrollWidth values for this
    // widget.
    if (this._scrollWidth != null) delete this._scrollWidth;
    if (this._scrollHeight != null) delete this._scrollHeight;

    // if we allow content to overflow, this method may change our drawn size.
    // We need to detect this case and fire 'resized'.
    // old scroll size was remembered last time this method was run (if overflow == "visible")
    // - hang onto this value locally for comparison with the current drawn size.
    
    var oldScrollWidth = this._currentContentWidth,
        oldScrollHeight = this._currentContentHeight;
        
    delete this._currentContentWidth;
    delete this._currentContentHeight;

    // hang onto a flag indicating whether we're overflowed
    var wasOverflowed = this._isOverflowed;
    this._isOverflowed = false;

	// make a local reference to the global Canvas object (faster)
	var canvas = isc.Canvas;
    
    // clear out the _delayedAdjustOverflow flag used by the delayedAdjustOverflow queueing code
    this._delayedAdjustOverflow = null;

    //>DEBUG
    if (this.getHandle() == null) this.logWarn("adjustOverflow: handle null");
    if (this.getClipHandle() == null) this.logWarn("adjustOverflow: clipHandle null");
    
    if (this.alwaysShowVScrollbar) {
        // this is acceptable since overflow may be modified at runtime
        if (this.overflow != isc.Canvas.AUTO || this.overflow != isc.Canvas.SCROLL) {
            this.logInfo("alwaysShowVScrollbar specified as true, but overflow set to \""+ 
                         this.overflow + "\". Property will be ignored.");
        } else if (this.showCustomScrollbars == false) {
            this.logWarn("alwaysShowVScrollbar property not supported when showing native scrollbars");
        }
    }

    if (this.logIsInfoEnabled(this._$sizing)) {
        this.logInfo("Specified size: " + this.getWidth() + "x" + this.getHeight() +
                     ", drawn scroll size: " + 
                            this.getScrollWidth(true) + "x" + this.getScrollHeight(true) +
                     ", border: " + this.getVBorderSize() + "x" + this.getHBorderSize() +
                     ", margin: " + this.getVMarginSize() + "x" + this.getHMarginSize() +
                     (oldScrollWidth == null ? "" :
                      ", old size: " + oldScrollWidth + "x" + oldScrollHeight) +
                     ", reason: " + reason, 
                     "sizing");
    }
                
    if (this.logIsDebugEnabled(this._$sizing)) {
        if (this.useClipDiv) {
            this.logDebug("clipHandle sizes: " + this.echoElementSize(this.getClipHandle()),
                          "sizing");
        } 
        this.logDebug("handle sizes: " + this.echoElementSize(this.getHandle()),
                      "sizing");
    }
    //<DEBUG
    
    
	if (this.overflow == canvas.IGNORE) {
        
	} else if (this.overflow == canvas.VISIBLE) {
            
        // If we drew larger than the specified size, expand to that size.  
        // Shrink if we were previously drawn larger than specified size, but never shrink
        // below specified size.
        
        
        if (this._resetHandleOnAdjustOverflow) {
            if (this.getWidth() < this.getVisibleWidth() || 
                this.getHeight() < this.getVisibleHeight()) 
            {
                this._setHandleRect(null, null, this.width, this._height);
            }
            delete this._resetHandleOnAdjustOverflow;
        }
        
        var scrollWidth = this.getScrollWidth(true),
            scrollHeight = this.getScrollHeight(true);

         
        if (this._useMozScrollbarsNone) { 
            var handle = this.getScrollHandle(); 
            if (handle.scrollTop != 0 || handle.scrollLeft != 0) { 
                handle.scrollTop = handle.scrollLeft = 0; 
                
            } 
        }

        // If the widget's content or children take up more space than the specified size, the
        // drawn size may exceed the specified size.

        
        
        var innerWidth = this.getInnerWidth(), innerHeight = this.getInnerHeight();
     
        // figure out whether we're overflowed, and store it
        var overflowed = this._isOverflowed = (scrollWidth > innerWidth ||
                                               scrollHeight > innerHeight);

        // if we're not overflowed, and we weren't overflowed before, we don't need to resize
        // the handle.
        if (!overflowed && !wasOverflowed) 
        {
            this._currentContentWidth = scrollWidth;
            this._currentContentHeight = scrollHeight;           
            //this.logWarn("adjustOverflow done, no overflow, size: " + 
            //             [scrollWidth, scrollHeight]);                         
            return;
        }
        
        //this.logWarn("proceeding to resize handle");

        // Resize to the larger of this.size and the reported scroll size [+ border and margin]
        // in each dimension.
        var hMarginBorder = this.getHMarginBorder(), vMarginBorder = this.getVMarginBorder();

        //this.logWarn("assigning width/height: " + [
        //                    Math.max((scrollWidth + hMarginBorder), this.getWidth()),
        //                    Math.max((scrollHeight + vMarginBorder), this.getHeight()) ] +
        //" margin/border is: " + [hMarginBorder,vMarginBorder]);
        this._setHandleRect(this.left, this.top, 
                            Math.max((scrollWidth + hMarginBorder), this.getWidth()),
                            Math.max((scrollHeight + vMarginBorder), this.getHeight()));

        
        var hasChildren = this.children && this.children.length > 0;
        if (!hasChildren || this.allowContentAndChildren) {
            var newScrollHeight = this.getScrollHeight(true), 
                newScrollWidth = this.getScrollWidth(true);
                
            if (newScrollHeight != scrollHeight || newScrollWidth != scrollWidth) {
                
            
                scrollWidth = newScrollWidth;
                scrollHeight = newScrollHeight;

                this._setHandleRect(this.left, this.top, 
                                Math.max((scrollWidth + hMarginBorder), this.getWidth()),
                                Math.max((scrollHeight + vMarginBorder), this.getHeight()));
                
            }
        }

        
        if (this.snapTo != null && overflowed && 
            (reason == this._$parentDrawn || reason == this._$draw)) 
        {
            this._resolvePercentageSize(true);
        }

        // Remember the current scrollWidth / scrollHeight so we can tell if future 
        // adjustOverflows change the drawn size.
        this._currentContentWidth = scrollWidth;
        this._currentContentHeight = scrollHeight;

        // if the scrollWidth or scrollHeight changed, for an overflow:visible widget this
        // indicates the visible height/width have changed, so fire resized().  Note that this
        // notification may fire for overflow being introduced, going away, or just changing.
        
        if ((oldScrollWidth != null && oldScrollWidth != scrollWidth) ||
            (oldScrollHeight != null && oldScrollHeight != scrollHeight)) 
        {
            // don't report overflow going away during a resize, it's redundant with the
            // resized() notification fired during resizeBy()
            
            if (!overflowed && reason == this._$resize) return;
            this._resized(scrollWidth - oldScrollWidth, scrollHeight - oldScrollHeight, 
                         this._$overflow);
        }
        
	} else if (this.overflow == canvas.HIDDEN) {

		// set the width and height of the layer explicitly
		this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());

        
        if (isc.Browser.isIE && this.isRTL()) {
            this.scrollLeft = this.getClipHandle().scrollLeft;
        }
        // If this adjustOverflow was fired because of a resize, our contents may no longer 
        // overflow by the same amount, meaning we may be 'scrolled off the end'
        // call clampToContent() to fix this
        // Note: May not be required for all browsers - but some, including Moz, do allow
        // specifying a scroll height such that you're scrolled past all content in the handle
        this._clampToContent();
        

	} else if (this.overflow == canvas.CLIP_H) {
        // adjust the clip to the specified values horizontally, no matter how large it
        // rendered 
        
        var scrollHeight = this.getScrollHeight(),
            vMarginBorder = this.getVMarginBorder(),
            drawnHeight = Math.max(scrollHeight + vMarginBorder, this.getHeight());
        
        this._currentContentHeight = drawnHeight;
        
        this.setClip(0, 
                     this.getWidth(), 
                     drawnHeight, 
                     0);            
        

		// set the width and height of the layer explicitly
        // (Setting the clip will not have changed the scrollHeight, so we don't need to pass
        // the calculateNewValues parameter in to force a new calculation).
		this._setHandleRect(this.left, 
							this.top, 
							this.getWidth(), 
							drawnHeight);
	} else if (this.overflow == canvas.CLIP_V) {
    
        var scrollWidth = this.getScrollWidth(),
            hMarginBorder = this.getHMarginBorder();

        // handle scrollWidth not being reported as less than specified width
        if ((isc.Browser.isIE || isc.Browser.isMoz || isc.Browser.isOpera) &&
            (scrollWidth > this.getInnerWidth()) &&
            (this._currentContentWidth == scrollWidth)) {
            // Resize to specified size, then check scrollWidth again, and resize a second time 
            // if necessary.
            this._setHandleRect(this.left, this.top, 
                                this.getWidth(), 
                                this.getHeight());

            // Recalculate the scrollWidth, and do a second resize, if it's greater
            // than the specified width now
            // (Pass in the 'calculateNewValues' parameter so it doesn't just return the
            // cached value).
            scrollWidth = this.getScrollWidth(true)
                
            if (scrollWidth > this.getInnerWidth()) {
                this._setHandleRect(this.left, this.top, 
                                    scrollWidth + hMarginBorder,
                                    this.getHeight());
            }
        
        // Other browsers / double resize not required...
        // The reported scrollWidth should be accurate - just resize to fit content        
        } else {    
            
    		// set the width and height of the layer explicitly
    		this._setHandleRect(this.left, 
                                this.top, 
                                Math.max(scrollWidth + hMarginBorder, this.getWidth()), 
                                this.getHeight());
        }
        
        var drawnWidth = Math.max(scrollWidth + hMarginBorder, this.getWidth());
        
		// adjust the clip to the specified values vertically, no matter how large it rendered
        this.setClip(0, 
                     drawnWidth, 
                     this.getHeight(),
                     0);
        
        // Remember the current scrollWidth
        this._currentContentWidth = drawnWidth;
        
	} else { // canvas.SCROLL, canvas.AUTO
        
        if (isc.Browser.isIE && this.showCustomScrollbars && 
            this.getScrollingMechanism() == isc.Canvas.NATIVE) 
        {
            var scrollLeft = this.scrollLeft, scrollTop = this.scrollTop;
            
            if (this.getScrollLeft() != scrollLeft || this.getScrollTop() != scrollTop) {
                this._handleCSSScroll();
            } 
        }

        // old state of the scrollbars
        var vscrollWasOn = this.vscrollOn,  
			hscrollWasOn = this.hscrollOn,
            wasFocusable = this._canFocus();
            
        // Permanently on v-scrollbar:
        var vScrollAlwaysOn = (this.alwaysShowVScrollbar && this.showCustomScrollbars);
        
		if (this.overflow == isc.Canvas.SCROLL) {
            // always show both scrollbars
			this.hscrollOn = this.vscrollOn = true;
		} else {	// Overflow is isc.Canvas.AUTO - determine whether scrollbars are required
    
            // scrollHeight / scrollWidth cache invalidated at the top of the method -no need for
            // 'calculateNewValues' parameter.

            var scrollHeight = this.getScrollHeight(),  height = this.getHeight(),
                scrollWidth = this.getScrollWidth(),    width = this.getWidth(),
                scrollbarSize = this.getScrollbarSize(),     scrollStateAtLayout;
            
            
            // If we're showing native scrollbars compare clientHeight / width with
            // specified height / width to determine whether we're showing scrollbars
            
            var vMarginBorder = this.getVMarginBorder(),
                hMarginBorder = this.getHMarginBorder();
            
            if (!this.showCustomScrollbars && this.getHandle().clientHeight != null) {
                this.hscrollOn = (this.getClipHandle().clientHeight < height - vMarginBorder);
                this.vscrollOn = vScrollAlwaysOn || 
                                 (this.getClipHandle().clientWidth < width - hMarginBorder);
            // Otherwise, we'll determine whether we need to show scrollbars in 2 steps:
            // If the content size exceeds the specified size, we definitely need 
            // scrollbars.
            } else {
                this.vscrollOn = vScrollAlwaysOn || 
                                 ((scrollHeight - (height - vMarginBorder)) > 0);
                this.hscrollOn = (scrollWidth - (width - hMarginBorder)) > 0;
            }
            if ((this.vscrollOn && !vscrollWasOn && !this.hscrollOn) || 
                (this.hscrollOn && !hscrollWasOn && !this.vscrollOn))
            {
                
                if (this.showCustomScrollbars) {
                    
                    this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());
                }

                // Call innerSizeChanged() to give the widget an opportunity to resize children to
                // match the new viewport.  Optimization: remember that we did this so we don't run
                // it redundantly if the scrolling state stays the same. 
                scrollStateAtLayout = (this.vscrollOn ? "V" : "") + (this.hscrollOn ? "H" : "");
                this.innerSizeChanged("introducing scrolling");
                
                // now that the content has been reflown, get the new dimensions (pass in the
                // 'calculateNewValue' parameter - the value will have changed since the last
                // calculation)
                var newScrollWidth = this.getScrollWidth(true),
                    newScrollHeight = this.getScrollHeight(true);
                //>DEBUG
                if (this.logIsDebugEnabled("scrolling")) {
                    this.logDebug("Rechecking scrollWidth/Height on introduction of scroll:" + 
                                  " old: " + [scrollWidth, scrollHeight] + 
                                  ", new: " + [newScrollWidth, newScrollHeight],
                                  "scrolling");
                } //<DEBUG
                scrollWidth = newScrollWidth;
                scrollHeight = newScrollHeight;
            }
            
            if (this.vscrollOn && !this.hscrollOn) {
                if (this.showCustomScrollbars || (this.getClipHandle().clientHeight == null))
                    this.hscrollOn = scrollWidth - (width - hMarginBorder - scrollbarSize) > 0;
                else
                    this.hscrollOn = 
                        (height > this.getClipHandle().clientHeight + this.getVBorderSize());
                    
            } else if (this.hscrollOn) {

                if (this.showCustomScrollbars || (this.getClipHandle().clientWidth == null))
                    this.vscrollOn = vScrollAlwaysOn || 
                                    (scrollHeight - (height - vMarginBorder - scrollbarSize) > 0);
                else 
                    this.vscrollOn = vScrollAlwaysOn ||  
                        (width > this.getClipHandle().clientWidth + this.getHBorderSize());
            }
        }

        //>DEBUG Report scroll state
        if (this.logIsInfoEnabled("scrolling")) {
            this.logInfo("Drawn size: " + this.getScrollWidth(true) + " by " + this.getScrollHeight(true) +
                         ", specified: " + this.getWidth() + " by " + this.getHeight() +
                         ", scrollbar state: " + (this.hscrollOn ? "h" : "") + 
                         (this.vscrollOn ? "v" : ""), "scrolling");
        } //<DEBUG

        
        if (this.showCustomScrollbars && 
            (this.hscrollOn != hscrollWasOn || this.vscrollOn != vscrollWasOn)) 
        {

            
            this._setHandleRect(this.left, this.top, this.getWidth(), this.getHeight());
            // Invalidate the cached scrollheight / width again..
            if (this._scrollWidth != null) delete this._scrollWidth;
            if (this._scrollHeight != null) delete this._scrollHeight;
        }

        var oldState = ((vscrollWasOn ? "V" : "") + (hscrollWasOn ? "H" : "")),
            newState = ((this.vscrollOn ? "V" : "") + (this.hscrollOn ? "H" : ""));
        if (oldState != newState) {
            //>DEBUG
            this.logInfo("Scrollbar state: " + oldState + " -> " + newState,
                         "scrolling"); //<DEBUG

            // call layout children since the viewport size changed
            // Optimization: if we ran innerSizeChanged() just above because a scrollbar was
            // newly introduced, and we're still in the same scrolling situation, no need to
            // run it again.
            if (scrollStateAtLayout == null || newState != scrollStateAtLayout) 
            {
                this.innerSizeChanged("scrolling state changed");
            }
        }

        
        if (this.isRTL() && this.hscrollOn && !hscrollWasOn) {
            var actualScroll = this.getClipHandle().scrollLeft;
            //this.logWarn("on RTL hscroll introduction, picked up scroll of: " + actualScroll + 
            //             ", was: " + this.scrollLeft);
            this.scrollLeft = actualScroll;
        }

        // if we're using native CSS scrollbars, we're done.  We just needed to figure out if
        // the browser was showing scrollbars.
        
		// if using custom scrollbars, show/hide scrollbars
		if (this.showCustomScrollbars) {
			// hide scrollbars if necessary here instead of later on, as calling scrollTo (see
			// below) will cause the other scrollbar to be redrawn if it isn't hidden yet.
			if (!this.hscrollOn && hscrollWasOn) this.hscrollbar.hide();
			if (!this.vscrollOn && vscrollWasOn) this.vscrollbar.hide();
            
			if (this.hscrollOn) {
				// if we need to scroll horizontally
				this._setHorizontalScrollbar();
			} else {
				// make sure we're not scrolled, scrollbar should already be hidden above.
				if (hscrollWasOn) this.scrollTo(0);
			}

			if (this.vscrollOn) {
				this._setVerticalScrollbar();
			} else {
				// make sure we're not scrolled, scrollbar should already be hidden above.
				if (vscrollWasOn) this.scrollTo(null, 0);
			}

            // Ensure we're not scrolled past our content            
            
            this._clampToContent();
        }
        
        // default focusability is based on whether a widget scrolls, so if we have introduced
        // or removed scrolling, focusability *may* have changed.  
        
        if ((this._useNativeTabIndex || this._useFocusProxy) && 
            wasFocusable != this._canFocus()) 
        {
            this._updateCanFocus();
        }
        
	}

	return true;
},

// called during adjustOverflow.
// if our scrollHeight / scrollWidth has changed such that we're scrolled off the
// end, snap back to the end
_clampToContent : function () {
    var maxScrollTop = Math.max(0, this.getScrollBottom()),
        maxScrollLeft = Math.max(0, this.getScrollRight()),
        newScrollLeft = this.getScrollLeft(),
        newScrollTop = this.getScrollTop(),
        clampToContent = false
    ;
    if (newScrollLeft > maxScrollLeft) {
        clampToContent = true;
        newScrollLeft = maxScrollLeft;
    }
    if (newScrollTop > maxScrollTop) {
        clampToContent = true;
        newScrollTop = maxScrollTop;
    }
    if (clampToContent) {
        this.scrollTo(newScrollLeft, newScrollTop);
    }

},

// Verify that the (native) scroll position of the widget matches the recorded 
// 'scrollLeft / scrollTop' properties. 
// If the positions do not match, will scroll to the specified scroll position.

checkNativeScroll : function () {
    var handle = this.getScrollHandle();
    if (this.getScrollingMechanism() != isc.Canvas.NATIVE || handle == null) return;

    if (handle.scrollLeft != this.scrollLeft || handle.scrollTop != this.scrollTop) {
        //this.logWarn("noticed handle scrolled to: " + 
        //             [handle.scrollLeft, handle.scrollTop]);
        // NOTE: not clear whether to respect the remembered position or not.  For an LV, it
        // would imply a redraw.  
        
        this.scrollTo(this.scrollLeft, this.scrollTop);
        //this.scrollTo(handle.scrollLeft, handle.scrollTop);
    }
},

//>	@method	canvas._setHorizontalScrollbar()	(A)
//			Creates a horizontal custom scrollbar on a widget
//          returns true for sucess, false for failure
//		@group	scrolling
//
//<
_setHorizontalScrollbar : function () {    

    // if the horizontal scrollbar hasn't been created, do so
    var scrollbar = this.hscrollbar;
    if (!scrollbar) {
        scrollbar = this.hscrollbar = isc.ClassFactory.newInstance(this.scrollbarConstructor,
		{
    		ID:this.getID()+"_hscroll",
    		autoDraw:false,
            _generated:true,
            zIndex:this.getZIndex() +1,
    		vertical:false,
    		scrollTarget:this,
            visibility:this.visibility,
    		_redrawWithMaster:false,
            _resizeWithMaster:false,
    		_redrawWithParent:false,
    		_selfManaged:false
        });
    }

    
	if (!isc.Page.isLoaded()) {
        var theCanvas = this;
		isc.Page.setEvent("load", function () { 
            if (!theCanvas.destroyed) theCanvas._setHorizontalScrollbar()
        });
		return;
	}

    // the need for scrolling may go away while we are waiting to draw
    if (!this.hscrollOn) return; 
    
    scrollbar.setRect(this.getOffsetLeft() + this.getLeftMargin() +
                        (this.vscrollOn && this.isRTL() ? this.scrollbarSize : 0),
                      this.getOffsetTop() + this.getHeight() - 
                              (this.getBottomMargin() + this.scrollbarSize),
                      this.getOuterViewportWidth(),
                      this.scrollbarSize);

    if (!scrollbar.masterElement) {
        // if we haven't added it as a peer yet, add it (which will draw it)
        this.addPeer(scrollbar);
    } else {
		// otherwise show it
		if (this.visibility != isc.Canvas.HIDDEN) scrollbar.show();
	}
},

//>	@method	canvas._makeVerticalScrollbar()	(A)
//			Creates a vertical custom scrollbar on a widget
//          returns true for sucess, false for failure
//		@group	scrolling
//<
_setVerticalScrollbar : function () {
    var scrollbar = this.vscrollbar
    if (!scrollbar) {
	    // if the vertical scrollbar hasn't been created, do so
		scrollbar = this.vscrollbar = isc.ClassFactory.newInstance(this.scrollbarConstructor,
		{
		    ID:this.getID()+"_vscroll",
			autoDraw:false,
            _generated:true,
            zIndex:this.getZIndex() +1,
			vertical:true,
			scrollTarget:this,
            visibility:this.visibility,
			_redrawWithMaster:false,
            _resizeWithMaster:false,
			_redrawWithParent:false, 
			_selfManaged:false
		});
    }

	// see _makeHorizontalScrollbar
	if (!isc.Page.isLoaded()) {
        var theCanvas = this;
		isc.Page.setEvent("load", function () { 
            if (!theCanvas.destroyed) theCanvas._setVerticalScrollbar()
        });
		return;
	}

    if (!this.vscrollOn) return;

    // make sure we're showing the corner if we should be doing so
    // this will mark as dirty if necessary
    scrollbar.setShowCorner(this.hscrollOn && this.vscrollOn);

    scrollbar.setRect(
        this.getOffsetLeft() + 
            (this.isRTL() ? this.getLeftMargin() : 
                            this.getWidth() - (this.getRightMargin() + this.getScrollbarSize())),
        this.getOffsetTop() + this.getTopMargin(),
        this.getScrollbarSize(), 
        this.getHeight() - this.getVMarginSize()
    );

    if (!scrollbar.masterElement) {
        // if we haven't added it as a peer yet, add it (which will draw it)
		this.addPeer(scrollbar);
    } else {
		// otherwise show it
		if (this.visibility != isc.Canvas.HIDDEN) scrollbar.show();
	}
},

// Scrollbar API
// -----------------------------------------------------------------------------------------
// Principally used by custom scrollbars

// scroll by slightly less than one viewport (less than in order to keep context)
scrollByPage : function (vertical, direction) {
    var distance = (vertical ? this.getViewportHeight() : this.getViewportWidth()) -
             this.scrollDelta;
    this._scrollByAmount(vertical, direction * distance);
},

// scroll by one (arbitrary) increment
scrollByDelta : function (vertical, direction) {
    this._scrollByAmount(vertical, direction * this.scrollDelta);
},

_scrollByAmount : function (vertical, amount) {
    if (vertical) {
        this.scrollTo(null, this.getScrollTop() + amount);
    } else {
        this.scrollTo(this.getScrollLeft() + amount);
    }
},

canScroll : function (vertical) {
    var scrollSize = vertical ? this.getScrollHeight() : this.getScrollWidth(),
        viewportSize = vertical ? this.getViewportHeight() : this.getViewportWidth();
    return (scrollSize > viewportSize);
},

// get the amount scrolled as a proportion of the maximum scroll amount, as a number between 0
// and 1
getScrollRatio : function (vertical) {
    var scrollSize = vertical ? this.getScrollHeight() : this.getScrollWidth(),
        viewportSize = vertical ? this.getViewportHeight() : this.getViewportWidth(),
        scrollPosition = vertical ? this.getScrollTop() : this.getScrollLeft(),
        // the furthest viewport position is when the viewport is showing the end of the
        // scrollable area
        maxScrollPosition = scrollSize - viewportSize;

    //this.logWarn("scrollSize: " + scrollSize +
    //             ", scrollPosition: " + scrollPosition);
    
    if (maxScrollPosition == 0) return 0;
    return scrollPosition / maxScrollPosition;
},

// scroll to some ratio of the maximum scroll amount
scrollToRatio : function (vertical, ratio) {
    var maxScroll = Math.max(0, (vertical ? this.getScrollBottom() : this.getScrollRight())),
        newCoord = Math.round(maxScroll * ratio);
    if (vertical) {
        this.scrollTo(null, newCoord);
    } else {
        this.scrollTo(newCoord);
    }
},

// get the ratio of the viewport size vs total content (used for thumb sizing)
getViewportRatio : function (vertical) {
    if (vertical) {
        return this.getViewportHeight() / this.getScrollHeight();
    } else {
        return this.getViewportWidth() / this.getScrollWidth();
    }
},

// Scrolling
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getScrollBottom()   ([])
// Returns the scrollTop required to scroll vertically to the end of this widget's content.
//      @visibility external
//		@group	scrolling
//<
getScrollBottom : function () {
    if (this.overflow == isc.Canvas.VISIBLE) return 0;
    return this.getScrollHeight() - this.getViewportHeight();
},

//>	@method	canvas.getScrollRight()   ([])
// Returns the scrollLeft required to scroll horizontally to the end of this widget's content.
//      @visibility external
//		@group	scrolling
//<
getScrollRight : function () {
    if (this.overflow == isc.Canvas.VISIBLE) return 0;
    return this.getScrollWidth() - this.getViewportWidth();
},


//>	@method	canvas.scrollToTop()   ([])
// Vertically scrolls the content of the widget to 0
//
//      @visibility external
//		@group	scrolling
//<
scrollToTop : function () {
    this.scrollTo(null, 0);
},
//>	@method	canvas.scrollToBottom()   ([])
// Vertically scrolls the content of the widget to the end of its content
//
//      @visibility external
//		@group	scrolling
//<
scrollToBottom : function () {
    this.scrollTo(null, this.getScrollBottom())
},

//>	@method	canvas.scrollToLeft()   ([])
// Horizontally scrolls the content of the widget to 0
//
//      @visibility external
//		@group	scrolling
//<
scrollToLeft : function () {
    this.scrollTo(0, null);
},
//>	@method	canvas.scrollToRight()   ([])
// Horizontally scrolls the content of the widget to the end of its content
//
//      @visibility external
//		@group	scrolling
//<
scrollToRight : function () {
    this.scrollTo(this.getScrollRight(), null);
},

//>	@method	canvas.scrollBy()   ([])
//  Scroll this widget by some pixel increment in either (or both) direction(s).
//
//      @visibility external
//      @param  dX  (number)    Number of pixels to scroll horizontally
//      @param  dY  (number)    Number of pixels to scroll vertically
//		@group	scrolling
//<
scrollBy : function (dX, dY) {
    var left, top;
    if (dX != null) left = this.getScrollLeft() + dX;
    if (dY != null) top = this.getScrollTop() + dY;
    
    return this.scrollTo(left, top);
},

//>	@method	canvas.scrollByPercent()   ([])
//  Scroll this widget by some percentage of scroll size in either (or both) direction(s).
//
//      @visibility external
//      @param  dX  (number | string)    Percentage to scroll horizontally. Will accept either
//                                      a numeric percent value, or a string like "10%".
//      @param  dY  (number | string)    Percentage to scroll horizontally. Will accept either
//                                      a numeric percent value, or a string like "10%".
//		@group	scrolling
//<
scrollByPercent : function (dX, dY) {
    if (isc.isA.String(dX)) dX = parseInt(dX);
    if (isc.isA.String(dY)) dY = parseInt(dY);
    
    // Resolve bad coordinates or null values to zero
    if (!isc.isA.Number(dX)) dX  = 0;
    else     
        // Note - "100%" scrolled is scrolled to the the scrollHeight - viewport height, as we're
        // moving the top / left edge of the viewport.
        dX = parseInt( dX / 100 * Math.max(0, (this.getScrollWidth()-this.getViewportWidth()) ) );
    if (!isc.isA.Number(dY)) dY  = 0;
    else 
        dY = parseInt( dY / 100 * Math.max(0, (this.getScrollHeight()-this.getViewportHeight()) ) );

    this.scrollBy(dX, dY);
},

//>	@method	canvas.scrollTo()   ([])
// Scrolls the content of the widget so that the origin (top-left corner) of the content
// is left pixels to the left and top pixels above the widget's top-left corner (but still
// clipped by the widget's dimensions).
// <p>
// This is guaranteed to be called whenever this Canvas is scrolled, whether scrolling is
// initiated programmatically, by custom scrollbars, or a by a native scrollbar.
//
//      @visibility external
//		@group	scrolling
//		@param	[left]	(number)    the left coordinate
//		@param	[top]	(number)    the top coordiante
//<
//>Animation additional 'animating' parameter passed if this is part of an animated scroll
//<Animation
scrollTo : function (left, top, handleAlreadyMoved, animating) {
//!DONTOBFUSCATE this function is legal to observe and grab parameters
   if (isc._traceMarkers) arguments.__this = this;
       
    //>Animation
    if (!animating) {
        if (this.scrollAnimation) this.finishAnimation("scroll");
        // We slide in and out of view by adjusting scroll positions - if we're in the middle
        // of such an animation, just suppress future scrolls.
        
        if (this.hideAnimation && this.$hideAnimationInfo.slideOut) 
            this.$hideAnimationInfo.slideOut = false;
        if (this.showAnimation && this.$showAnimationInfo.slideIn)
            this.$showAnimationInfo.slideIn = false;
    }
    //<Animation

    //>DEBUG
    if (this.logIsDebugEnabled("scrolling")) {
        this.logDebug("scrollTo(" + left + ", " + top + 
                      (handleAlreadyMoved ? ", handleAlreadyMoved" : "") + ")", "scrolling");
    } //<DEBUG

	if (!isc.isA.Number(left)) left = this.getScrollLeft();
	if (!isc.isA.Number(top)) top = this.getScrollTop();
 
    // if scrolling is actually occuring
    var actuallyMoved = false;
    if ((left != null && left != this.scrollLeft) || (top != null && top != this.scrollTop)) {
        actuallyMoved = true;
        // save off the last scroll coordinates, to allow detecting scrolling direction
        this.lastScrollLeft = this.scrollLeft;
        this.lastScrollTop = this.scrollTop;
        //this.logWarn("left, top: " + [left, top] + 
        //             ", scrollLeft/Top: " + [this.scrollLeft, this.scrollTop]);
        this.lastScrollDirection = (left != null && left != this.scrollLeft && 
                                    top != null && top != this.scrollTop ? "both" :
                                    top != null && top != this.scrollTop ? "vertical" :
                                    "horizontal");
    }

    // The handleAlreadyMoved parameter means we're responding to a native onscroll event.
    // the viewport has already been scrolled by the browser - we're just being notified, and
    // we only call this method for the sake of observers, and to update custom scrollbar
    // thumbs. In this case, or if we haven't yet been drawn, just store the passed in values.
    if (handleAlreadyMoved || !this.isDrawn()) {
        this.scrollLeft = left;
        this.scrollTop = top;
    } else {
        // Don't scroll past the ends of the widget - this way the callers don't have to worry
        // about passing in good parameters.
        // Also in IE in RTL mode, scrollLeft and scrollTop can be negative (presumably for
        // Native CSS scrolling)
        var maxScrollLeft = this.getScrollRight();
        this.scrollLeft = Math.max(0, Math.min(maxScrollLeft, left));
        var maxScrollTop = this.getScrollBottom();
        this.scrollTop = Math.max(0, Math.min(maxScrollTop, top));

        

        // Actually scroll the widget.
        this._scrollHandle(this.scrollLeft, this.scrollTop);
    }

    // update thumb position and size.  NOTE: because scrollbar construction is delayed under
    // some circumstances, we might not have a scrollbar yet even if scroll is on.
    if (this.showCustomScrollbars) {
        if (this.hscrollOn && this.hscrollbar) this.hscrollbar.setThumb();
        if (this.vscrollOn && this.vscrollbar) this.vscrollbar.setThumb();
    }

    // fire notification of scroll change
    if (actuallyMoved) this._scrolled();        
},  

//> @method canvas.scrolled()
// Notification that this component has just scrolled.  Use with
// +link{class.observe,observation}.
// <P>
// Fires for both CSS and +link{Scrollbar,"synthetic" scrollbars}.
//
// @group scrolling
// @visibility external
//<
// include a default implementation so observation will work
scrolled : function () {},

_scrolled : function () {
    // If the mouse is over us and we scrolled (for example due to mouse wheel scroll / drag scroll)
    // fire a synthetic mousemove event on the event target
    // This means we can react to the fact that the mouse's position over our content has changed
    // (EG: update styling on list grid rows as the user scrolls with the mouse-wheel)
    
    if (!isc.EH._handlingMouseMove) {
        var lastEvent = isc.EH.lastEvent,
            currentlyOver = (lastEvent.eventType == isc.EH.MOUSE_MOVE ? lastEvent.target : 
                                                                        isc.EH.lastMoveTarget);
        if (currentlyOver != null) {
            // We only want to fire a mouse move if we are the current mouse target (or a parent of it)
            
            if (!this.contains(currentlyOver, true)) currentlyOver = null;
            else if (lastEvent.eventType != isc.EH.MOUSE_MOVE && 
                     !currentlyOver.visibleAtPoint(isc.EH.getX(), isc.EH.getY(), false)) 
            {
                 currentlyOver = null; 
            }
    
            
             if (currentlyOver != null) isc.EH._handleMouseMove(null, isc.EH.lastEvent);
        }
    }
    if (this.scrolled) this.scrolled();
},

//>	@method	canvas.scrollToPercent()   ([])
//  Scroll this widget to some position specified as a percentage of scroll size in either 
// (or both) direction(s).
//
//      @visibility external
//      @param  left (number | string)    Left Percentage position to scroll to 
//                                        Will accept either a numeric percent value, or a 
//                                        string like "10%".
//      @param  top (number | string)    Top Percentage position to scroll to 
//                                       Will accept either a numeric percent value, or a 
//                                       string like "10%".
//		@group	scrolling
//<
scrollToPercent : function (left, top) {
    if (isc.isA.String(left)) left = parseInt(left);
    if (isc.isA.String(top)) top = parseInt(top);
    
    // Resolve bad coordinates or null values to zero
    if (!isc.isA.Number(left)) left  = 0;
    if (!isc.isA.Number(top)) top  = 0;
    
    // Note - "100%" scrolled is scrolled to the the scrollHeight - viewport height, as we're
    // moving the top / left edge of the viewport.
    left = parseInt( left / 100 * Math.max(0, (this.getScrollWidth()-this.getViewportWidth()) ) );
    top = parseInt( top / 100 * Math.max(0, (this.getScrollHeight()-this.getViewportHeight()) ) );
    
    this.scrollTo(left, top);
},
    
//>	@method	canvas._scrollHandle()   (IA)
// Internal method to scroll the widget's viewport to the left / top coordinates passed in.
// Called by canvas.scrollTo();
//
//      @visibility internal
//		@group	scrolling
//		@param	left	(number)    the left coordinate
//		@param	top	(number)    the top coordiante
//      @see    scrollTo()
//<
_scrollHandle : function (left, top) {    
    var scrollMechanism = this.getScrollingMechanism();
    
    // for browsers that support setting scrollLeft/scrollTop to scroll.
    if (scrollMechanism == isc.Canvas.NATIVE) {
        
        var handle = this.getScrollHandle();
		if (handle) {
            // set a flag to tell '_handleCSSScroll' to No-Op while the scroll is in progress
            this._scrollingHandleDirectly = true;

			handle.scrollLeft = left;
			handle.scrollTop = top;

            delete this._scrollingHandleDirectly;
            
            

            
            if (handle.scrollLeft != this.scrollLeft || handle.scrollTop != this.scrollTop) {

                //this.logWarn("handle clamping scrollLeft/Top at: " + 
                //             [handle.scrollLeft, handle.scrollTop] + 
                //             " tried to assign: " + [this.scrollLeft, this.scrollTop]);
                this.scrollLeft = handle.scrollLeft;
                this.scrollTop = handle.scrollTop;
            }
    
		} 

    // scrolling mechanisms for browsers in which assigning to handle.scrollLeft/scrollTop
    // doesn't work.
    } else if (scrollMechanism == isc.Canvas.NESTED_DIV) {
        // move the contentDiv around within the clipDiv to create scrolling
        
        // Note that the contentDiv's parent is the clipDiv, and the contentDiv is always drawn
        // at (0,0) within the clipDiv, so we don't worry about left and top with respect to a
        // parent, since that applies only to the clipDiv and it's parent.  

        // get the contentDiv
        var handle = this.getHandle();
        if (handle == null) {
            //>DEBUG this happens to ListGrid headers in NS6 when the LV scrolls it on
            // LV.draw().  
            this.logWarn(this.getCallTrace(arguments) + " in NS6 with null handle");
            //<DEBUG
            return;
        }
        // Grab the style attribute for the handle
        handle = handle.style;

        //this.logWarn("handle is at: " + [handle.left, handle.top] + 
        //             ", moving to: " + [-this.scrollLeft, -this.scrollTop]);
        // move it within the clipDiv to create scrolling
        handle.left = -left + "px";
        handle.top = -top + "px";
    
	}

},


// Handle a native scroll event

_handleCSSScroll : function (waited, fromFocus) {
    isc.EH._setThread("SCR");

    if (isc._traceMarkers) arguments.__this = this;

    // The contents of the Canvas have already been scrolled by the browser, and we're just 
    // being notified of it.  

     
    if (this._scrollingHandleDirectly) return;

    //>Moz 
    
    if (isc.Browser.isMoz && !waited && (fromFocus ||  isc.Browser.geckoVersion < 20030312)) {
        if (!this._scrollTimeout)
            this._scrollTimeout = this.delayCall("_handleCSSScroll", [true], 10);
        return;
    }
    this._scrollTimeout = null;
    // Avoid attempting to handle a delayed scroll if the widget in question has been cleared
    if (!this.isDrawn()) return;
    //<Moz

    var handle = this.getScrollHandle(),
        trueScrollLeft = handle.scrollLeft,
        trueScrollTop = handle.scrollTop;
        
    // our notion of the scroll position matches the DOM's - just return
    if (trueScrollLeft == this.scrollLeft && trueScrollTop == this.scrollTop) return;

    
    var scrollMechanism = this.getScrollingMechanism();
    if (scrollMechanism != isc.Canvas.NATIVE) {
        this.logWarn("unsupported native scroll occurred on this widget - resetting");
        if (scrollMechanism == isc.Canvas.NESTED_DIV) {
            handle.scrollLeft = handle.scrollTop = 0;
        } else {
            handle.scrollLeft = this.scrollLeft;
            handle.scrollTop = this.scrollTop;
        }
        return;
    }

    // Even though the native element has already scrolled, we call scrollTo to update
    // this.scrollLeft/Top, and to cause any scrollTo overrides or observations to fire
    
    this.scrollTo(trueScrollLeft, trueScrollTop, true);

    isc.EH._clearThread();
},


mouseWheel : function () {
    if ((this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL) &&
        this.showCustomScrollbars && this.vscrollOn) 
    {
        var wheelDelta = this.ns.EH.lastEvent.wheelDelta;

        // For each increment the user scrolled the mouse wheel, we want to move about 50px
        // This seems to approximately match native scrolling speed.
        var scrollTo = this.scrollTop + Math.round(wheelDelta * isc.Canvas.scrollWheelDelta);
        
        // Note that scrollTo already catches scrolling past beginning or end
        this.scrollTo(this.getScrollLeft(), scrollTo);

        // return false to cancel further / native processing
        return false;
    }
    
    // Not a scrollable region, return true
    return true;
},

// Helpers to determine if this was a 'fast' scrolling mechanism - track scroll / drag scroll
isDragScrolling : function () {
    if (this.vscrollOn && this.vscrollbar && this.vscrollbar.isDragScrolling()) return true;
    if (this.hscrollOn && this.hscrollbar && this.hscrollbar.isDragScrolling()) return true;
    return false;
},

isRepeatTrackScrolling : function () {
    if (this.vscrollOn && this.vscrollbar && this.vscrollbar.isRepeatTrackScrolling()) return true;
    if (this.hscrollOn && this.hscrollbar && this.hscrollbar.isRepeatTrackScrolling()) return true;
    return false;
},

// Default Keyboard Handling
// --------------------------------------------------------------------------------------------
// Canvases have built-in scrolling and focus change behavior for keyboard events

//>	@method	canvas.handleKeyPress()     (A)
//  Canvas level handler function for the (possibly bubbled) keyPress event, fired by the event
//  handling system when the user presses a key on a focus'd widget.
//  Fires any user-defined 'keyPress' handler string method.
//  Scrolls the widget on arrow keypresses
//      @group  events
//
//      @param  event   (ISC Event object)
//      @param  eventInfo   (object)
//<

handleKeyPress : function (event, eventInfo) {
    var keyPressReturn;

    // If a keypress string method handler is defined, call it before firing standard scrolling
    // logic
    if (this.convertToMethod("keyPress")) {
        keyPressReturn = this.keyPress(event, eventInfo);
    }
    
    
    if (keyPressReturn != false && this.shouldCancelKey != null  && 
        this.shouldCancelKey(event, eventInfo)) 
    {
        keyPressReturn = false;
    }
        
    if (keyPressReturn == false) return false;
    
    var keyName = event.keyName;
    
    if (this._useFocusProxy && 
        ((isc.Browser.isMoz && this.canSelectText) || isc.Browser.isSafari) 
        && keyName == "Tab") 
    {
        this.setFocus(true);
    }   
  
    
    // if using custom scrollbars, scroll if standard scrolling keys are hit 
    
    if ((this.overflow == isc.Canvas.AUTO || this.overflow == isc.Canvas.SCROLL) && 
         this.showCustomScrollbars) 
    {
        var leftDelta = 0, topDelta = 0;

        // pageUp/Down: scroll one viewport
        if (keyName == "Page_Up") topDelta -= this.getViewportHeight();
        else if (keyName == "Page_Down") topDelta += this.getViewportHeight();
        // arrows: scroll a small amount
        else if (keyName == "Arrow_Up") topDelta -= 10; // Maybe make this a var?
        else if (keyName == "Arrow_Down") topDelta += 10;
        else if (keyName == "Arrow_Left") leftDelta -= 10;
        else if (keyName == "Arrow_Right") leftDelta += 10;
       
        if (leftDelta != 0 || topDelta != 0) {
            // NOTE: scrollTo automatically clamps
            this.scrollTo(this.scrollLeft + leftDelta, this.scrollTop + topDelta);
            // return false so the event doesn't get propogated
            return false;
        } 

        // Home / End - go to the top or bottom
        if (keyName == "Home") {
            this.scrollTo(null, 0);
            return false;
        } else if (keyName == "End") {
            this.scrollTo(null, (this.getScrollHeight() - this.getViewportHeight()));
            return false;
        }
    
    }
    return keyPressReturn;
},

// --------------------------------------------------------------------------------------------

//>	@method	canvas._setHandleRect()	(A)
//			(internal) routine to set the rectangle of the canvas handle to its 
//          .left, .top, .width, .height
//		@group	positioning, sizing
//
//		@param	left	(number)
//		@param	top		(number)
//		@param	width	(number)
//		@param	height	(number)
//<


_setHandleRect : function (left, top, width, height) {
    

    
    
    // In RTL mode, we need to shift the handle to the right of the v-scrollbar if appropriate
    if (this.showCustomScrollbars && this.vscrollOn && left != null && this.isRTL()) {
        left += this.getScrollbarSize();
        //this.logWarn("adjusting left coordinate for RTL:" + left + this.getStackTrace());
    }

    // Call the '_adjustHandleSize' helper method to determine the width/height 
    // values we will actually apply to the handle to get the desired size.
    // This corrects for the space required by custom scrollbars, and for border, padding, etc.
    if (width != null || height != null) {
    
        var adjustedSize = this._adjustHandleSize(width, height);
        
        width = adjustedSize[0];
        height = adjustedSize[1];
    }
    //this.logWarn("assigning size of: " + [width, height]);
     
    var styleHandle = this.getStyleHandle();
    if (styleHandle) {
        if (isc.isA.Number(left)) this._assignSize(styleHandle, isc.Canvas.LEFT, left);
        if (isc.isA.Number(top)) this._assignSize(styleHandle, isc.Canvas.TOP, top);
        if (isc.isA.Number(width)) this._assignSize(styleHandle, this._$width,
                                                    Math.max(width,1));
        if (isc.isA.Number(height)) this._assignSize(styleHandle, this._$height,
                                                     Math.max(height,1));
        //  this.logWarn("setHandleRect: style handle now reports: " + 
        //              this.echo({left:styleHandle.left, top:styleHandle.top, 
        //                         width:styleHandle.width, height:styleHandle.height}));
    }
},

_$px : "px",
_assignSize : function (styleHandle, prop, size) {
    if (isc.Browser.isIE || isc.Browser.isOpera) {
        if (!isc.Browser.isStrict) {
            
            styleHandle[prop] = size;
        } else {
            // strict mode IE JS errors if a negative height or width is set
            if (size < 0 && (prop == this._$width || prop == this._$height)) size = 0;
            styleHandle[prop] = size + this._$px;
        }
    } else {
        if (styleHandle == null) {
            
            //this.logWarn(" size: " + size + ", styleHandle is: " + styleHandle);
            return;
        }
        styleHandle[prop] = size + this._$px;
    }
},

_sizeBackMask : function () {
    var backMask = this._backMask;
    if (!backMask) return;

    if (this.showEdges) {
        // keep the backmask from "squaring out" edges that use transparency
        var edge = this._edgedCanvas,
            // If maskEdgeCenterOnly is set, mask only the area of the center segment of the
            // edges.  Any content that overlaps the edges will still burn through.  This
            // allows a translucent Window header and other uses cases, with the drawback that
            // those areas won't be masked.
            center = this.maskEdgeCenterOnly,
            left = center ? edge._leftEdge : edge._leftMargin,
            right = center ? edge._rightEdge : edge._rightMargin,
            top = center ? edge._topEdge : edge._topMargin,
            bottom = center ? edge._bottomEdge : edge._bottomMargin,
            width = this.getVisibleWidth() - (left + right),
            height = this.getVisibleHeight() - (top + bottom);
        // NOTE: this can happen when the edgedCanvas is a background and the widget sizes
        // so that only edges are visible, eg rounded minimized window
        if (width <= 0 || height <= 0) backMask.hide();
        else {
            if (this.isVisible()) backMask.show();
            //this.logWarn("sizing backmask to: " + [width, height] + this.getStackTrace());
            backMask.setRect(this.getLeft() + left,
                             this.getTop() + top,
                             width,
                             height);
        }
    } else {
        backMask.setRect(this.getRect());
    }
},

// Text Direction
// --------------------------------------------------------------------------------------------
// Bi-directional text (BIDI) support for languages that read right to left (RTL)



//>	@method	canvas.getTextDirection()
//		Get the text direction of this canvas.
//		This property is determined according to the containment hierarchy
//		 (like disabled) and is ultimately set by the page property if
//		 not defined by any widget.
//
//		@group	appearance
//		@platformNotes	IE only.
//		@return	(TextDirection)	direction -- Canvas.LTR or Canvas.RTL
// @visibility internal
//<

getTextDirection : function () {
	// start off by looking in this object
	var target = this;
		
	// while the target exists
	while (target) {
	    // if the is not enabled, return false
		if (target.textDirection != null) return target.textDirection;
		// otherwise look up the parent chain
		target = target.parentElement;
		// and if an eventProxy is defined, use that instead of the parentElement
		if (target && target.eventProxy) target = target.eventProxy;
	}
	// if no widget specified a textDirection, use the Page.textDirection
	return isc.Page.getTextDirection();
},

//> @method canvas.isRTL()
// Return whether the text direction is right to left
// @return (boolean) whether text direction is RTL
//<
isRTL : function () {
    return (this.getTextDirection() == isc.Canvas.RTL);
},

//> @method canvas.getRTLSign()
// Get either one or negative one if the text direction is LTR or RTL respectively.
// <P>
// Useful for writing LTR/RTL component layout algorithms.
//
// @return (Number) 1 for LTR, -1 for RTL
//<
getRTLSign : function () {
    return this.isRTL() ? -1 : 1;
},

// Visibility
// ------------------------------------------------------------------------------------------------
// Whether this Canvas is currently visible

//>	@method	canvas.setVisibility()	(A)
//			set the visibility of this object
//		@group	visibility
//
//		@param	newVisibility 	(string) 	CSS visibility to set to (Canvas.HIDDEN,
//		                                    Canvas.VISIBLE, etc)
//								(boolean)	false == hide, anything else == show
//<
setVisibility : function (newVisibility) {
    //>Animation
    // Finish any hide/show animations that are running
    
    if (this._animatingHide != null && !this._hidingAsAnimation) 
        this.finishAnimation(this._animatingHide);
    if (this._animatingShow != null && !this._showingAsAnimation) 
        this.finishAnimation(this._animatingShow);
    // if we have any opacity change animation(s) running, finish them before changing our
    // visibility
    if (this.fadeAnimation) this.finishAnimation("fade");
    //<Animation

	// if newVisibility is a boolean, normalize to a CSS value
	if (!isc.isA.String(newVisibility)) {
        newVisibility = (newVisibility != false ? isc.Canvas.INHERIT : isc.Canvas.HIDDEN);
    }

    // no-op if no change in visibility
    if (this.visibility == newVisibility) return;

    // check if we're currently visible (NOTE: must call isVisible to check parents)
    var wasVisible = this.isVisible(); 

	// set the visible state of the object
	this.visibility = newVisibility;

	// if we're drawn
	if (this.isDrawn()) {

		if (!wasVisible && this.isVisible()) {
            
            // If we're showing a widget that is awaiting redraw, or has a child awaiting redraw,
            // redraw before showing to avoid a flash after show().
            if (this.isDirty()) {
                this.redraw("show() while dirty");

            } else if (this.children && this.children.length > 0) {

                // check for dirty children and redraw them.  Use the redraw queue to determine
                // whether we have any dirty children - quicker than iterating down through all
                // our children recursively. 

                // Make a copy of the Queue, as redrawing widgets inside it will change it's
                // length, etc.
                var origRedrawQueue = isc.Canvas._redrawQueue.duplicate();
                
                // Note - we redraw any children directly rather than just redrawing the
                // parent, because it's more efficient -- while the parent is likely to have
                // no significant content, redrawing it could force a redraw of a number of
                // siblings
                for (var i = 0; i < origRedrawQueue.length; i++) {
                    var widget = origRedrawQueue[i];
                    // If we're the parent of a dirty child, redraw it.
                    // the isDirty() check verifies that we haven't cleaned it up by redrawing
                    // a parent in a prev iteration of this loop.
                    // Note - it's ok to leave the widget in the redraw queue, as
                    // clearRedrawQueue() will skip any widgets that are no longer dirty
                    if (widget && widget.isDirty() && this._isVisibilityAncestorOf(widget)) {
                        widget.redraw("show() on parent while dirty");
                    }
                }
            }

            
            this._updateHandleDisplay();

		}

        this._setHandleVisibility(newVisibility);
	}
	
    // update handle.display if using hideUsingDisplayNone
    if (wasVisible && !this.isVisible()) {
        this._updateHandleDisplay();
    }

	// if we have peers, show or hide them as well
	if (this.peers) {
		for (var i = 0; i < this.peers.length; i++) {
            var peer = this.peers[i];
            // special case the scrollbars: they should generally hide and show with the
            // master, but sometimes we hide a scrollbar because we no longer need to scroll on
            // that axis, and we don't want it to get show()n when we show().
            if (this.isVisible() &&
                ((peer == this.hscrollbar && !this.hscrollOn) ||
                 (peer == this.vscrollbar && !this.vscrollOn))) continue;
            // don't show the shadow if we're set to no longer have one (eg temporary drag
            // shadow)
            if (this.isVisible() && peer == this._shadow && !this.showShadow) continue;
            if (peer._showWithMaster) peer.setVisibility(newVisibility);
		}
	}

    // notify children that visibility changed
    if (this.children) this.children.map("parentVisibilityChanged", newVisibility);

    if (this.parentElement) this.parentElement.childVisibilityChanged(this, newVisibility);
    
    //>FocusProxy
    // If we have a 'focusProxy' make sure it has the appropriate visibility
    if (this._useFocusProxy) this._updateFocusProxyVisibility();
    //<FocusProxy
},

// tell our children some parent's visibility changed
parentVisibilityChanged : function (newVisibility) {
    if (this.children) this.children.map("parentVisibilityChanged", newVisibility);

    
    this._updateHandleDisplay();

    //>EditMode if we had the resize thumbs, hide them
    if (this == isc.Canvas._thumbTarget) isc.Canvas.hideResizeThumbs();
    //<EditMode

    // If we have a 'focusProxy', make sure it has the appropriate visibility
    if (this._useFocusProxy) this._updateFocusProxyVisibility();
},

// notification that a child's visibility changed
childVisibilityChanged : function (child, newVisibility) {
    // NOTE: if a child uses display:none or some other custom way of hiding itself, this
    // *might* reduce content size.
    this._markForAdjustOverflow("childVisChange");
},

// notifications that a child/peer was cleared
childCleared : function (child) { if (!this.destroying) this._markForAdjustOverflow("childClear"); },
peerCleared : function (peer) { },

childDrawn : function (child) {    
    if (this.isDrawn()) this._markForAdjustOverflow("childDraw")
},
peerDrawn : function (peer) {},

//>FocusProxy
// If this widget has a 'focusProxy' - ensure it is shown and hidden with the widget

_updateFocusProxyVisibility : function () {
    if (!this._useFocusProxy || !this._hasFocusProxy) return;
    
    var isVisible = this.isVisible(),
        proxy = this._getFocusProxyHandle();
    if (proxy) {            
        if (isVisible && proxy.style.visibility == isc.Canvas.HIDDEN) 
            proxy.style.visibility = isc.Canvas.VISIBLE
        if (!isVisible && proxy.style.visibility != isc.Canvas.HIDDEN)
            proxy.style.visibility = isc.Canvas.HIDDEN
    }
},
//<FocusProxy

//>	@method	canvas._setHandleVisibility()	(A)
//			(internal) routine to set the visibility of the underlying DOM element.  Call
//			           setVisibility instead.
//		@group	visibility
//
//		@param	newVisibility 	(string) 	CSS visibility constant
//<
_setHandleVisibility : function (newVisibility) {
    var handle = this.getStyleHandle();
    if (handle != null) handle.visibility = newVisibility;

},

_$none:"none",
_updateHandleDisplay : function () {
    if (!this.hideUsingDisplayNone || !this.isDrawn()) return;

    var handle = this.getStyleHandle();

    if (!this.isVisible() && !this._setToDisplayNone) {
        // save off the current state of the display property so we can restore it when the
        // component becomes visible again
        this._visibleDisplayStyle = handle.display;
        this._setToDisplayNone = true;
        handle.display = this._$none;
        
    } else if (this.isVisible() && this._setToDisplayNone) {
        // if the display property had a value other than the empty string when it was in
        // visible state (picked up when we hide) we use that
        handle.display = (this._visibleDisplayStyle ? this._visibleDisplayStyle :
                                                      isc.emptyString);
        delete this._setToDisplayNone;                                                      
    }
},

// Helper method: should we draw() on a call to show()?
// Returns true for top level widgets that are not peers
// (in which cases parent/master is responsible for drawing at the right time).
// Also, don't draw if we are drawn, or already in the middle of drawing 
// (This can happen if a child tries to show it's parent while the parent is drawing it).
_drawOnShow : function () {
    return (this.getDrawnState() == isc.Canvas.UNDRAWN) && 
           !this.parentElement && !this.masterElement;
},

//>	@method	canvas.show()   ([])
// Sets this widget's visibility to "inherit", so that it becomes visible if all it's parents
// are visible or it has no parents.
// <P>
// If the widget has not yet been drawn (and doesn't have a parent or master), this method
// calls the draw method as well.
//
//      @visibility external
//		@group	visibility
//      @example    showAndHide
//<
show : function () {
    if (isc._traceMarkers) arguments.__this = this;

    var showWithFocus = this.hasFocus;

	// if we haven't yet been drawn, go ahead and do so for top level widgets
    if (this._drawOnShow()) {
        // note: passing the parameter to draw will prevent draw calling show() again when it is
        // done drawing.
        this.draw(true);
	} 
    	
	this.setVisibility(isc.Canvas.INHERIT);

    if (showWithFocus && this.hasFocus) {
        //>DEBUG if we were marked as having focus before being drawn, and still are, focus
        // explicitly
        this.logInfo("Show: Hidden / Undrawn widget marked as having focus - calling focus()", 
                     "events"); //<DEBUG
        
        this.hasFocus = false;
        this.focus();
    }
    
    if (this.autoShowParent && this.parentElement) this.parentElement.show();
},

_$relative:"relative",
_relativePageResized : function () {
    if (!this.isDrawn() || this.parentElement || this.position != this._$relative) return;
        
    // Fire completeMoveBy() - this will handle all our notifications such as
    // masterMoved() on our peers.
    var oldLeft = this._preResizePageLeft,
        oldTop = this._preResizePageTop,
        pageLeft = this.getPageLeft(),
        pageTop = this.getPageTop();
    
    this._moveDeltaX = (pageLeft - oldLeft);
    this._moveDeltaY = (pageTop- oldTop);
    this._completeMoveBy();
     
    // re-register for the next resized event
    this._preResizePageLeft = pageLeft;
    this._preResizePageTop = pageTop;
    isc.Page.setEvent(
        "resize", 
        this,
        isc.Page.FIRE_ONCE,
        "_relativePageResized"
    );
    
    
},


//>	@method	canvas.hide()   ([])
//			Sets the widget's CSS visibility attribute to "hidden".
//      @visibility external
//		@group	visibility
//      @example    showAndHide
//<
hide : function () {
    this._updateFocusForHide();
	this.setVisibility(isc.Canvas.HIDDEN);
},

//>	@method	canvas.isVisible()  ([])
// Returns true if the widget is visible, taking all parents into account, so that a widget
// which is not hidden might still report itself as not visible if it is within a hidden
// parent.
// <P>
// NOTE: Undrawn widgets will report themselves as visible if they would be visible if drawn.
//
//      @visibility external
//		@group	visibility
//
//		@return	(boolean)	true if the widget is visible, false otherwise
//<
isVisible : function () {
	// start off by looking in this object
	var target = this;
	
	// while the target exists
	while (target) {
		// if the is not visible, return false
		if (target.visibility == isc.Canvas.HIDDEN) return false;

		// if the is explicitly set as visible, return true
		if (target.visibility == isc.Canvas.VISIBLE) return true;
        
		// otherwise "inherit", so look up the parent chain
		target = target.parentElement;
	}
	// if everyone is inheriting visiblility (up to the page itself), return true
	return true;
},

// _isDisplayNone()
// Internal method - returns true if this canvas or any of its ancestors is currently rendered
// with display:"none"
_isDisplayNone : function () {
    var target = this;
    while (target) {
        if (target.visibility == isc.Canvas.HIDDEN && target.hideUsingDisplayNone) return true;
        target = target.parentElement;
    }
    return false;
},


// Enable/Disable
// -----------------------------------------------------------------------------------------------
//>	@groupDef enable
// Disabled components do not respond to mouse or keyboard events, and change appearance to
// indicate they are disabled.
// @title Enabling and Disabling
// @visibility external
//<

//> @method canvas.setEnabled()     (A)
// set the enabled state of this object.
//
// @group enable
// @param  newState (boolean) pass false to disable or anything else to enable
// @visibility external
// @deprecated As of Smartclient version 5.5, deprecated in favor of +link{canvas.setDisabled()}
//<
_$disabled:"disabled",
setEnabled : function (newState) {
    this.logWarn("call to deprecated method 'setEnabled()' - use 'setDisabled()' instead."
                
    );
    var disabled = ((newState == null || isc.isA.Boolean(newState)) ? !newState 
                    : (newState == this._$disabled));

    this.setDisabled(disabled);
},

//>	@method	canvas.setDisabled()	(A)
// set the disabled state of this object
// @group enable
// @param disabled (boolean) new disabled state of this object - pass <code>true</code> to disable the widget
// @visibility external
//<
setDisabled : function (newState) {
    
    // We can no-op if we're already explicitly set to the appropriate state.
    if (newState == null) newState = false;
    if (!isc.isA.Boolean(newState)) newState = (newState == this._$disabled);
    if (this.disabled == newState) return;
    
    
	// Notify peers that the master element has been disabled
    // Will disable the peers too where appropriate
	if (this.peers) this.peers.map("masterDisabled", newState);
    
    // disabled state is inherited in a similar way to hidden visibility - if any of this widgets
    // ancestors are disabled, it's considered disabled, even if the "disabled" property has
    // been set to true.
    // Use isDisabled() to check whether this state change needs to have any effect on the
    // widget's handle and its children.
    var wasDisabled = this.isDisabled()
	// set the disabled state of the object
	this.disabled = newState;
    var isDisabled = this.isDisabled();

    if (wasDisabled != isDisabled) {
    
        // update our HTML to reflect a change of state.
        this.setHandleDisabled(isDisabled);
    
        // If we have any children, they will also be effected by the state change.
        if (this.children) this.children.map("parentDisabled", isDisabled);
    }     
},

// Notification function called when a widget's masterElement gets disabled
masterDisabled : function (disabled) {
    // Simply update our disabled state to match our masters, since we don't actually inherit
    // enabled /disabled state from our master like children do.
    this.setDisabled(disabled);
},

// notification function fired when a widget's parent or ancestor gets disabled or enabled

parentDisabled : function (disabled) {
    // If we're explicitly disabled the parent's enabling/disabling will not effect us - we 
    // will remain disabled
    if (this.disabled) return;
    
    // If the parent redrew on being disabled we don't need to update our HTML
    if (!this.parentElement.redrawOnDisable) this.setHandleDisabled(disabled);
    
    // Tell our descendants about the change
    if (this.children) this.children.map("parentDisabled", disabled);
},


// Helper to actually update our HTML for becoming disabled / enabled
setHandleDisabled : function (disabled) {
    if (!this.isDrawn()) return;
    
    if (this.redrawOnDisable) this.markForRedraw("setDisabled");
        
    if (this._canFocus()) this.disableKeyboardEvents(disabled);
    
    
},

disableKeyboardEvents : function (disabled, recursive) {

    if (disabled) {
        // these methods will No-Op if this._useNativeTabIndex is false, or if the widget
        // is not drawn.
        this._setHandleTabIndex(-1);
        if (this.accessKey != null) this._setHandleAccessKey(null);
    } else {
        // restore them when we're enabling them!
        this._setHandleTabIndex(this.getTabIndex());
        if (this.accessKey != null) this._setHandleAccessKey(this.accessKey);            
    }

    // On disable, do a blur
    // Do this even if we're redrawing since that's done asynchronously
    if (disabled && this.hasFocus) this.blur();
    
    if (recursive && this.children) {
        for (var i = 0; i < this.children.length; i++) {
            this.children[i].disableKeyboardEvents(disabled, true);
        }
    }
},

//>	@method	canvas.enable() ([])
// Enables this widget and any children / peers of this widget.
//  @visibility external
//  @group enable
//<
enable : function () {
    
	// if the object is not already enabled
	if (this.disabled) this.setDisabled(false);
},


//>	@method	canvas.disable()    ([])
// Disables this widget and any children and peers of this widget.
//  @visibility external
//  @group enable
//<
disable : function () {
	if (!this.disabled) this.setDisabled(true);
},


//>	@method	canvas.isDisabled()  ([])
// Is this canvas disabled? Note that the disabled state is inherited - this method will return
// true if this widget, or any of its ancestors are marked disabled.
//  @visibility external
//  @group enable
//  @return	(boolean)   true if the widget or any widget above it in the containment hierarchy
//                      are disabled.
//<
isDisabled : function () {
    // Check this widget and each ancestor of it for the 'disabled' property
	var target = this;
	while (target) {
		if (target.disabled) return true;
        
		target = target.parentElement;
		// and if an eventProxy is defined, use that instead of this object
		if (target && target.eventProxy) target = target.eventProxy;
	}
	// if no-one is disabled, return false
	return false;
},

//> @method canvas.isEnabled()  ([])
// Returns true if the widget and all widgets above it in the containment hierarchy are enabled.
// Returns false otherwise.
// @visibility external
// @group enable
// @return    (boolean)   true if the widget and all widgets above it in the containment hierarchy
//                      are enabled; false otherwise
// @deprecated As of SmartClient version 5.5 deprecated in favor of +link{canvas.isDisabled()}.
//<
isEnabled : function () {
    this.logWarn("Call to deprecated 'isEnabled()' method - should use isDisabled() instead");
    return !this.isDisabled();
},

// Focus
// --------------------------------------------------------------------------------------------
//> @groupDef focus
// Focus is the ability to become the target of keyboard input events.
// <P>
// A widget normally receives focus by being clicked on or tabbed to.
//
// @title Focus
// @visibility external
//<

// Internal method to determine whether this widget should accept keyboard focus.
_canFocus : function () {
    // respect explicit setting
    if (this.canFocus != null) return this.canFocus;
    
    // If this.canFocus is not set, allow focus only on scrollable canvii with visible
    // scrollbars
    if ((this.overflow == isc.Canvas.SCROLL) || 
        ((this.overflow == isc.Canvas.AUTO) && (this.vscrollOn || this.hscrollOn)) ) {
            return true;
    }
    
    return false;
},

//>	@method	canvas.setCanFocus()	(A)
// Change whether a widget can accept keyboard focus. 
// @param canFocus (boolean) whether the widget should now accept focus
// @see canFocus
// @visibility external
//<
setCanFocus : function (canFocus) {
    this.canFocus = canFocus;
    this._updateCanFocus();
},

// Internal method to update the widget's handle to allow / disallow keyboard focus, based on
// the result of this._canFocus()
// Will No-Op if we're not using native tab-index / focus behavior, either by writing handlers
// onto the widget handle, or using the focusProxy approach.
_updateCanFocus : function () {
    this._updateHandleForFocus(this._canFocus());
    this.canFocusChanged();
},

_updateHandleForFocus : function (canFocus) {
    
    // If we're using native tab index (tabIndex and accessKey properties written into the widget
    // handle), we need to clear them out / update them.
    // This means updating the the handle's tabIndex / onfocus/onblur handler and accessKey.
    //
    // If we're using a focusProxy DOM element, we need to update (or create / destroy) it.
    //
    // If we're not leveraging any native browser tabIndex behavior, but a widget is being
    // removed from the tab-order, we need to clear up it's references in the auto-assigned tab
    // index system.
    var handle;

    //>FocusProxy if we're using a focusProxy, make sure it's present or absent as necessary
    if (this._useFocusProxy) {
        if (canFocus) {
            handle = this._getFocusProxyHandle();
            
            // if the focusProxy doesn't exist, call the makeFocusProxy method to create it.
            // Notes:
            // - the handle will be created in the correct state, so we return
            // - creation is delayed in some browsers
            if (!handle) return this.makeFocusProxy();
        } else {
            // we shouldn't be focusable, so destroy our focusProxy if we have one, and remove
            // ourselves from the tab order
            this._clearFocusProxy();
            
            return;
        }
     
        // we're focusable and we already have a focusProxy.
        // In Safari - when tabIndex is -1 we don't write a focusProxy into the DOM.
        // If tabIndex is -1 and we already have a focus proxy, clear it here
        
        if (isc.Browser.isSafari && this.getTabIndex() == -1) {
            this._clearFocusProxy();
            return;
        }
    }
    //<FocusProxy
    
    
    if (this._useAccessKeyProxy()) {
        if (canFocus && this.accessKey) {     
            this._makeAccessKeyProxy();
        } else if (this._accessKeyProxy) {
            this._clearAccessKeyProxy();
        }
    }
    
    // If we're writing focus properties directly onto the widget handle, get a pointer to that
    // for manipulation below.
    if (this._useNativeTabIndex) handle = this.getClipHandle();

    if (canFocus) {
        
        // Note: this.getTabIndex() will set this._autoTabIndex as appropriate.
        // _setTabIndex() will fall through to _setHandleTabIndex() and apply the tabIndex to 
        // the handle if appropriate.
        //
        
        this._setTabIndex(this.getTabIndex(), this._autoTabIndex);
        
        // if we can accept focus, setup handlers and put us in the tab order
        if (handle != null) {
            
            var focusHandler = this._getNativeFocusHandlerMethod(),
                blurHandler = this._getNativeBlurHandlerMethod();
            handle.onfocus = focusHandler;
            handle.onblur = blurHandler;
            if (this.accessKey) this._setHandleAccessKey(this.accessKey);
        }
                    
        
    } else {
        // if we can't accept focus, clear handlers and remove us from the tab order
        if (handle != null) {
            
            handle.onFocus = null;
            handle.onBlur = null;

            // Remove from the tab order
            
            this._setHandleTabIndex(-1);    
            
            if (handle.accessKey != null) this._setHandleAccessKey(null);
        }
        
    }
},

// notification fired when the canFocus status of this widget has updated
canFocusChanged : function () {
    var parent = this.parentElement;
    while (parent) {
        parent.childCanFocusChanged(this);
        parent = parent.parentElement;
    }
},
// Notification fired when the canFocus status of some child of this widget changes
// Fired by 'canFocusChanged'.  No Op by default.

childCanFocusChanged : function (child) {
},

// update whether or not we should show the focusOutline
// handleOnly param will update the handle to show / hide the focus outline but leave
// this.showFocusOutline unmodified 
_$none:"none",
setShowFocusOutline : function (showFocusOutline, handleOnly) {
    if (!handleOnly && this.showFocusOutline == showFocusOutline) return;
    if (!handleOnly) this.showFocusOutline = showFocusOutline;
    if (isc.Browser.isMoz) {
        var handle = this.getClipHandle();
        if (handle) {
            handle.style.MozOutlineStyle = (showFocusOutline ? isc.emptyString : this._$none);
        }
    } else {
        var handle = this.getHandle();
        if (handle) handle.hideFocus = !showFocusOutline;
    }
    
},

////////
// obtain or lose the focus in this object
//
//	if this object wants to get the focus when it's clicked, set:
//		obj.canFocus = true
//
//	to have an object redraw when its focus changes, set:
//		obj.redrawOnFocus = true
////////

//_readyToSetFocus: can we update the focus state of this widget?
_readyToSetFocus : function (focus) {
    
    
    return (this.isDrawn() && this.visibleInDOM() && (!focus || !this.isDisabled()));
},

visibleInDOM : function () {
    if (!this.isVisible()) return false;
    
    // relative positioned widgets can be hidden without us ever being notified of the hide, 
    // if the ancestors in the DOM have been hidden
    // If the topParent is pos:"relative", iterate up from the topParent to the page body and verify
    // all elements are visible
    
    var topWidget = this;
    while (topWidget.parentElement) topWidget = topWidget.parentElement;
    
    if (topWidget.position == isc.Canvas.ABSOLUTE) return true;
    
    var docBody = this.getDocumentBody();
    var handle = topWidget.getClipHandle().parentNode;
    while (handle && handle != docBody) {
        var style = handle.style;
        if (style && style.visibility == this._$hidden) return false;
        if (style && style.display == this._$none) return false;
        handle = handle.parentNode;
    }
    return true;
},

// get the handle this widget uses for focus, if there is one
getFocusHandle : function () {
    if (this._useNativeTabIndex) {
        return this.getClipHandle();
    //>FocusProxy
    } else if (this._useFocusProxy && this._hasFocusProxy) {
        return this._getFocusProxyHandle();
    //<FocusProxy
    }
    return null;
},

//>	@method	canvas.setFocus()	(A)
// set the focused state of this object
//		@group	focus
//
//	@param	newState			(boolean) pass false to blur or anything else to focus
//<
setFocus : function (newState) {
    if (!this._readyToSetFocus(newState)) return;
    var focusHandle = this.getFocusHandle();

    

    // call the EventHandler method to update the current focus canvas
    if (newState && this._canFocus()) {
    
        // If the widget is currently masked we should avoid calling handle.focus(), and instead
        // notify the EH directly so it can update it's 'maskedFocusCanvas'
        // Because of IE's fun asynchronous focus handler behavior we'll have to also catch the 
        // case where focus has previously been called on another widget, but the focus handler
        // hasn't been fired yet and avoid it clobbering this maskedFocusCanvas setting when
        // onfocus does fire.

        if (focusHandle != null) {
            /*
            if (isc.EH._unconfirmedFocus == this) {
                // aborting here has not yet been observed to matter
                this.logDebug("ignoring focus attempt on widget with unconfirmed focus()");
                return;
            }
            */
            //>DEBUG
            this.logInfo("about to call native focus()" +
                         (this.logIsDebugEnabled("traceFocus") ? this.getStackTrace() : ""), 
                         "nativeFocus");
            //<DEBUG
            
            isc.EH._unconfirmedFocus = this;
            focusHandle.focus();
            isc.EH._lastFocusTarget = this;
        } else {
            this.ns.EH.focusInCanvas(this);
        }

    } else if (this.hasFocus) {
        if (focusHandle) {
            /*
            if (isc.EH._unconfirmedBlur == this) {
                // aborting here has not yet been observed to matter
                this.logWarn("ignoring blur attempt on widget with unconfirmed blur()");
                return;
            }
            */
            //>DEBUG
            this.logInfo("about to call native blur()" + 
                         (this.logIsDebugEnabled("traceBlur") ? this.getStackTrace() : ""), 
                         "nativeFocus");
            //<DEBUG
            
            isc.EH._unconfirmedBlur = this;
            focusHandle.blur();
        } else {
            this.ns.EH.blurFocusCanvas(this);
        }
    }

    
},


_restoreFocus : function () {
    // abort if focus has moved (onFocus() fired somewhere else)
    var focusCanvas = isc.EH._focusCanvas;
    if (focusCanvas != null && focusCanvas != this) {
        this.logDebug("not restoring focus; focus moved to: " + focusCanvas,
                      "nativeFocus");
        return;
    }
    // abort if we've called native focus() to move focus elsewhere, but onFocus() hasn't fired
    // yet
    var pendingFocus = isc.EH._unconfirmedFocus;
    if (pendingFocus != null && pendingFocus != this) {
        this.logDebug("not restoring focus; focus about to move to:" + pendingFocus,
                      "nativeFocus");
        return;
    }
    this.logDebug("restoring focus from zIndex change", "nativeFocus");
    this._setFocusWithoutHandler(true);
},

//>	@method	canvas.focus()
// If this canvas can accept focus, give it keyboard focus. After this method, the canvas
// will appear focussed and will recieve keyboard events.
// @group	focus
// @visibility external
//<
focus : function () {

    if (isc._traceMarkers) arguments.__this = this;
    
    this.setFocus(true);
},


//>	@method	canvas.blur()
// If this canvas has keyboard focus, blur it. After this method, the canvas
// will no longer appear focussed and will stop recieving keyboard events.
// @group	focus
// @visibility external
//<
blur : function () {
    if (isc._traceMarkers) arguments.__this = this;
    this.setFocus(false);
},

// focusAtEnd(): Helper method for synthetic tabIndex stuff - puts focus at the 'beginning' or
// 'end' of this widget. 
// No effect unless this widget has some concept of focusable sub elements, for which it
// manages the tab order directly. 
// Has meaningful implementation in DynamicForm / Toolbar by default 
focusAtEnd : function (start) {
    return this.focus();
},

//>	@method	canvas._setFocusWithoutHandler()
//			Internal method to update whether this widget has focus or not, without triggering the
//          focusChanged handler
//		@group	focus
//      @visibility internal
//<
_setFocusWithoutHandler : function (state) {
    this._suppressFocusChanged = true;
    this.setFocus(state);
    
    
},


//>	@method	canvas._focusChanged() (I)
// Fired when this canvas is focussed or blurred.  May cause redraw if redrawOnFocus is true.
//		@group	focus
//<
 
_focusChanged : function (hasFocus) {
    if (hasFocus == null) hasFocus = (this.ns.EH._focusCanvas == this);
    this.hasFocus = hasFocus;

    if (this._suppressFocusChanged) {
        delete this._suppressFocusChanged;
        return false;
    }

    // have a flag so the focusChanged so we know if we're firing in response to a focus/blur
    
    this._focusChanging = true;
    
    // if defined, call the focusChanged handler (stringMethod)
    if (this.focusChanged != null) {

        this.convertToMethod("focusChanged");
        this.focusChanged(hasFocus);
    }
    
	// if we're marked to redraw when focused, redraw!
	if (this.redrawOnFocus) this.markForRedraw("setFocus");
    this._focusChanging = false;
},

// if we have a focusOnHide target specified, focus in it, otherwise just blur
// Note: We're hiding ourselves and all our children here, so if we're hiding the current
// focus canvas we need to respect it's focusOnHide (if set)
_updateFocusForHide : function () {

    var fc = this.ns.EH.getFocusCanvas();
    
    if (this._isVisibilityAncestorOf(fc)) {
    
        if (isc.isA.Canvas(fc.focusOnHide) && fc.focusOnHide.isDrawn() && 
            fc.focusOnHide.isVisible()) {
                fc.focusOnHide.focus();
        }
        else {
            fc.blur();
            // In IE blur() is asynchronous - the blur handler won't fire until after the 
            // end of the calling thread.
            // If the handle has disappeard from the DOM, it won't fire at all, which can leave
            // EH focusCanvas pointing to a hidden (or even destroyed) widget.
            // Explicitly call EH.blurFocusCanvas() now if we still have focus at this point
            // to avoid this. When the native blur handler fires EH._blurFocusCanvas will no-op
            // if appropriate.
            if (fc.hasFocus) isc.EH.blurFocusCanvas(fc);
        }
    }        
},

//> @method canvas.containsFocus()
// Returns true if the keyboard focus is in this Canvas or any child of this Canvas.
// @return (boolean) whether this Canvas contains the keyboard focus
// @group focus
// @visibility external
//<
containsFocus : function  () {
    var fc = this.ns.EH.getFocusCanvas();
    return this.contains(fc, true); 
},




// Access Key
// --------------------------------------------------------------------------------------------
// Global keyboard shortcuts for switching focus to this Canvas

//>	@method	canvas.setAccessKey()	(A)
// Set the accessKey for this canvas.
// <P>
// The accessKey can be set to any alphanumeric character (symbols not supported)
// Having set an accessKey, the canvas will be given focus when the user hits 
// Alt+[accessKey], or in Mozilla Firefox 2.0 and above, Shift+Alt+[accessKey].
//
//		@group	focus
//
//      @param accessKey
//              Character to use as an accessKey for this widget.  Case Insensitive.
// @visibility external
//<

setAccessKey : function (accessKey) {
    this.accessKey = accessKey;

    // Only set the accessKey on the handle if the widget is really focus-able
    if (this._canFocus() && !this.isDisabled()) {
        this._setHandleAccessKey(this.accessKey)
    }
},

// Internal method to actually set the accessKey on the widget handle (or focusProxy if
// appropriate).  No-op's if the handle is not drawn or if _useNativeTabIndex and
// _useFocusProxy are both false
_setHandleAccessKey : function (accessKey) {

    // accessKeyProxy stuff for Moz
    if (this._useAccessKeyProxy()) {
        if (accessKey == null) this._clearAccessKeyProxy();
        else {
            if (this._accessKeyProxy) this._accessKeyProxy.accessKey = accessKey;
            else this._makeAccessKeyProxy();
        }
        return;
    }
    
    if (this._useNativeTabIndex) {
        var handle = this.getHandle();
        if (handle != null) handle.accessKey = accessKey;
    }
    
    if (this._useFocusProxy && this._hasFocusProxy) {
        var handle = this._getFocusProxyHandle();

        if (handle != null) {
            
            if (isc.Browser.isMoz) {
                //>FocusProxy
                this._clearFocusProxy();
                this.makeFocusProxy();
                //<FocusProxy
            } else {
                handle.accessKey = accessKey;
            }
        }
    }
        
},

// Return the accessKey for the widget.
getAccessKey : function () {
    return this.accessKey;
},


// Tab Index
// --------------------------------------------------------------------------------------------
// Managing this Canvas position in the global tabbing order

// Return the Tab index for the widget.
// If not set, defaults to isc.Canvas.currentTabIndex, which increments from 1000 as more widgets
// are added to the tab order.
getTabIndex : function () {
    if (this.tabIndex == null) {
        this._autoAllocateTabIndex();
    }

    return this.tabIndex;
},


// Most widgets take up 1 tab index.
getTabIndexSpan : function () {
    return 1;
},

//>	@method	canvas.setTabIndex()	(A)
//  Assign an explicit tabIndex to this widget.
// @param tabIndex (number) New tabIndex for this widget. Must be less than 
//                          +link{Canvas.TAB_INDEX_FLOOR} to avoid interfering with auto-assigned
//                          tab indices on the page.
// @group focus
// @see canvas.tabIndex
// @visibility external
//<
// See comments by _autoAllocateTabIndex() for ISC tab order tracking implementation details.
setTabIndex : function (index) {
    var floor = isc.Canvas.TAB_INDEX_FLOOR;
    // explicitly specified tabIndices must be below a certain floor so that they don't collide
    // with ISC auto-allocated tab indices.
    if (index >= floor) {
        var minIndex = floor - 1;
        this.logWarn("setTabIndex(): Passed index of " + index + 
                     ". This method does not support setting a tab index greater than " 
                     + minIndex +
                     ".  Setting tab index for this widget to " + minIndex +
                     this.getStackTrace());                     
        index = minIndex;
    }                     
    // Update the _previousTabWidget and _nextTabWidget flags
    this._removeFromAutoTabOrder();
    this._setTabIndex(index, false);
},

_setTabIndex : function (index, autoAllocated) {
    //!DONTCOMBINE

    // Note - this method doesn't manage _previousTabWidget and _nextTabWidget - those
    // properties should be updated by the calling methods.

    this._autoTabIndex = autoAllocated;

    this.tabIndex = index;
    if (this._canFocus() && !this.isDisabled()) {
        this._setHandleTabIndex(index);
    }
},

// _setHandleTabIndex
// Updates the tabIndex of a widget's handle -- will not effect the widgets 'tabIndex' property
// No-ops if it can't get to the widget's handle, or if _useNativeTabIndex is not true
_setHandleTabIndex : function (index) {
    if (this._useNativeTabIndex && this.isDrawn()) {    
        this.getClipHandle().tabIndex = index;
        
        if (isc.Browser.isIE) isc.Canvas._forceNativeTabOrderUpdate();
    }
    
    //>FocusProxy
    if (this._useFocusProxy) {
        
        // We may not have a focus proxy yet - this could happen if we're calling this method
        // before delayed creation of a focus proxy, or in Safari going from tabIndex -1 to
        // a positive t.i.
        if (!this._hasFocusProxy) return this.makeFocusProxy();
        
        var handle = this._getFocusProxyHandle();
        // before manipulating the handle take focus from it
        // If necessary we'll restore focus after changing the tabIndex
        
        
        var hasFocus = (this.hasFocus && !this._focusChanging);
        if (hasFocus && handle) handle.blur();
        
        // In safari, it is impossible to make a native focus proxy and exclude it from the
        // page's tab order, so we just clear it to remove the widget from the tab order of the
        // page.
        if (isc.Browser.isSafari && index < 0) return this._clearFocusProxy();
        
        
        if (handle != null) {        
            handle.tabIndex = index;
            
            if (isc.Browser.isMoz) {
                handle.style.MozUserFocus = (index < 0 ? "ignore" : "normal");
            }
            
            if (hasFocus) handle.focus();
        }
    }
    //<FocusProxy
},


// If no tabIndex is specified for the widget (and it is focus-able), automatically 
// assign one.
// In IE, tabIndex can validly be any integer from -32767 to 32767
// Negative values are ommitted from the tab order.
//
// We manage auto-assigning tab indexes for widgets in the following way:
// A widget is auto-assigned a tabIndex at draw time.  We start the ISC tab index count at 1000,
// and increment this value by 50 for each new widget.
// Each widget can be assumed to take up a 'span' of tab index slots - by default one slot, but
// for a dynamic form one slot per focusable item. The method 'getTabIndexSpan()' should return
// this value.

//
// If a developer wants to explicitly specify tab-index for some widgets, they can set the 
// values to anything below 1000 to avoid interfering with the auto-assigned tab indexes. 
// If the widget is a dynamic form, the developer should be aware that the form items will take 
// up slots in the page's tab order, so they may have to leave gaps between DynamicForm widgets.
// We may want to make 'getTabIndexSpan()' external for this reason.
//
// For widgets with auto-assigned tab indexes, we allow the widgets to keep track of where they
// are in the tab order by setting pointers to the next and previous widget in the tab order
// (_nextTabWidget and _previousTabWidget).  
// We also support runtime reordering of auto-assigned tabindex widgets via the internal
// _setTabBefore() and _setTabAfter() methods. This is used to ensure that Layout members
// appear in the appropriate order on the page


_autoAllocateTabIndex : function () {
    var Canvas = isc.Canvas;
    if (Canvas._currentTabIndex == null) {
        Canvas._currentTabIndex = Canvas.TAB_INDEX_FLOOR;
    }
    
    var currentTabWidget = isc.EH._lastTabWidget;
    if (currentTabWidget) Canvas._currentTabIndex += currentTabWidget.getTabIndexSpan();

    // Always leave a significant gap between widgets when first setting them up - makes
    // it easier to slot other widgets in between them in the page's tab order.
    Canvas._currentTabIndex += Canvas.TAB_INDEX_GAP
    
    // If we hit the native browser tabindex ceiling warn about it
    
    if (Canvas._currentTabIndex > isc.Canvas.TAB_INDEX_CEILING && 
        !isc.Canvas._tabIndexCeilingWarned) 
    {
        isc.Canvas.logWarn("Auto allocation of tab-indices has reached native browser ceiling " +
                           "- tab-order cannot be guaranteed for widgets on this page.");
        isc.Canvas._tabIndexCeilingWarned = true;
    }
    this._setTabIndex(Canvas._currentTabIndex, true);

    // update the flags to point to the previous widget in the auto-allocated tab order
    if (currentTabWidget) {
        currentTabWidget._setNextTabWidget(this);
        this._previousTabWidget = currentTabWidget;
    } else {
        isc.EH._firstTabWidget = this;
    }
    isc.EH._lastTabWidget = this;
},

// limitation: if you try to move before or after a widget with an explicitly
// specified tab index we can't manage the order properly.
// Also updates any auto-assigned children's tab indexes to keep them in order within the page
// level tab order.
// If this method is called on a non-focusable widget, it will only update any focusable 
// children.

_setTabBefore : function (otherWidget) {
    // No need to take action if attempting to move before this, or if we're already before the
    // other widget
    if (this == otherWidget || this._getNextTabWidget() == otherWidget) return;
    
    // note: getTabIndex() will set the _autoTabIndex flag if no tab index was explicitly
    // specified
    var newTabIndex = otherWidget.getTabIndex();    
    if (!otherWidget._autoTabIndex) {
        //>DEBUG
        this.logWarn("_setTabBefore() attempting to set tab index adjacent to widget " 
                    + otherWidget + " with explicitly specified tabIndex [" + otherWidget.tabIndex 
                    + "]. This method can only manipulate widgets with auto-assigned tab indexes.");
        //<DEBUG
        return;
    }
    
    var previousWidget = otherWidget._previousTabWidget;
    
    
    
    // Remove this widget from it's current position in the tab order - 
    // If we have a previous / next widget in the tab order, update it's flag pointing back to us
    // to point to the appropriate widget after / before us in the tab order
    var prev = this._getPreviousTabWidget(),
        next = this._getNextTabWidget();
    if (isc.EH._lastTabWidget == this) isc.EH._lastTabWidget = prev;
    if (isc.EH._firstTabWidget == this) isc.EH._firstTabWidget = next;
    if (prev != null) 
        prev._setNextTabWidget(next);
    if (next != null)
        next._setPreviousTabWidget(prev);
        
    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);
    
    this._slotTabBetween(otherWidget._getPreviousTabWidget(), otherWidget);

    // If we have children, ensure they show up after us in the tab order.
    
    this._slotChildrenIntoTabOrder();
},

_setTabAfter : function (otherWidget) {

    // No need to take action if attempting to move before this, or if we're already before the
    // other widget
    if (this == otherWidget || this._previousTabWidget == otherWidget) return;


    // note: getTabIndex() will set the _autoTabIndex flag if no tab index was explicitly specified
    otherWidget.getTabIndex();

    if (!otherWidget._autoTabIndex) {
        //>DEBUG
        this.logWarn("_setTabAfter() attempting to set tab index adjacent to widget " 
                    + otherWidget + " with explicitly specified tabIndex [" + otherWidget.tabIndex 
                    + "]. This method can only manipulate widgets with auto-assigned tab indexes.");
        //<DEBUG
        return;
    }
    
    var previousWidget = otherWidget,
        oldPrev = this._getPreviousTabWidget(),
        oldNext = this._getNextTabWidget();

    if (isc.EH._lastTabWidget == this) isc.EH._lastTabWidget = oldPrev;
    if (isc.EH._firstTabWidget == this) isc.EH._firstTabWidget = oldNext;        
    
    // If we have a previous / next widget in the tab order, update it's flag pointing back to us
    // to point to the appropriate widget after / before us in the tab order
    if (oldPrev != null) 
        oldPrev._setNextTabWidget(oldNext);
    if (oldNext != null)
        oldNext._setPreviousTabWidget(oldPrev);
    
    // (and clear these flags on us!)
    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);        
    
    // Now just slot in between the other widget and the one that follows it
    this._slotTabBetween(otherWidget, otherWidget._getNextTabWidget());
    
    // If we have children, ensure they show up after us in the tab order.
    this._slotChildrenIntoTabOrder();    
},

// Insert this widget into the auto-assigned tab order between 2 other widgets
_slotTabBetween : function (previous, next) {

    // This will automatically set up EH._lastTabWidget
    if (next == null) return this._autoAllocateTabIndex();  
    
    // We can't easily slot in front of the first widget in the tab-order - in this case we 
    // need to slot into the first widgets slot and shift the first widget forward to avoid 
    // assigning a tabIndex < the isc-managed tabIndex floor.
    if (previous == null) {
        var nextNext = next._getNextTabWidget();
        next._removeFromAutoTabOrder();
        this._setNextTabWidget(nextNext);
        this._setPreviousTabWidget(null);
        this._setTabIndex(next.tabIndex, true);
        isc.EH._firstTabWidget = this;
        
        next._slotTabBetween(this, nextNext);
        return;
    }

    // At this point we have 2 valid tabindex-adjacent widgets to slot between

    // update the _nextTabWidget / _previousTabWidget flags
    this._setNextTabWidget(next);
    next._setPreviousTabWidget(this);
    this._setPreviousTabWidget(previous);
    previous._setNextTabWidget(this);
    
    var previousTabIndex = previous.tabIndex + previous.getTabIndexSpan(),
        nextTabIndex = next.tabIndex,
        // split the difference between the previous widgets tabIndex (plus its required slots)
        // and the next tabIndex.
        newTabIndex = previousTabIndex + Math.floor((nextTabIndex - previousTabIndex)/2), 
        span = this.getTabIndexSpan();
    // if our required tabIndexSpan overlaps the next tabIndex, we need to shift the next widget
    // forwards
    
    if ((newTabIndex + span) > nextTabIndex) {
        next._shiftTabIndexForward((newTabIndex + span) - nextTabIndex);
        // This calculation is now guaranteed to give us enough space
        //this.logWarn("Our span:" + span +": not enough tabIndex gap between:" 
        //                + previous + ", " + previous.tabIndex
        //                + " and next:"+ next + ":" + nextTabIndex + 
        //                  " - resolved by shifting next to:"+ next.tabIndex);
        
    }
    if (this.logIsDebugEnabled("tabIndex")) {
        this.logDebug("Putting " + this.getID() + " in tab order between: "+ previous.getID() + 
                     ":"+ previous.tabIndex + ", and :"+ next.getID() + ":" + next.tabIndex +
                     ". Resulting tabIndex:"+ newTabIndex, "tabIndex");
    }
    
    this._setTabIndex(newTabIndex, true);
},


// Shunt our tabIndex forward by the number of slots passed in.
// If we don't have room without colliding with the tabIndex of this._nextTabWidget,
// shift that widget forward as well (recursively)
_shiftTabIndexForward : function (minimumRequired) {

    
    
    var next = this._getNextTabWidget();
    if (next == null) {
        this._setTabIndex(this.tabIndex + minimumRequired + isc.Canvas.TAB_INDEX_GAP, true);
        return;
    }
    
    // Shunt ourselves right up against the guy after us by default.
    // If that doesn't give us enough room we'll have to shift him forward too.
    var nextTI = next.getTabIndex(),
        newTI = nextTI - this.getTabIndexSpan();
    if (this.tabIndex + minimumRequired < newTI) this._setTabIndex(newTI, true);
    else {
        // Shift the next widget forward by the space we need to shift forwards by less the
        // space we actually can shift forwards by
        next._shiftTabIndexForward(minimumRequired - (newTI - this.tabIndex));
        // And now when we shunt up against it we know there will be enough space.
        this._setTabIndex(next.tabIndex - this.getTabIndexSpan(), true);
    }
},


_getNextTabWidget : function (backwards) {
    if (!backwards) return this._nextTabWidget;
    else return this._previousTabWidget;
},

_getPreviousTabWidget : function () {
    return this._getNextTabWidget(true);
},

_setNextTabWidget : function (widget, backwards) {

    if (!backwards) this._nextTabWidget = widget;
    else this._previousTabWidget = widget;
},

_setPreviousTabWidget : function (widget) {
    return this._setNextTabWidget(widget, true);
},

_focusInNextTabElement : function (forward, mask) {
    var nextWidget = this;
    do {
        nextWidget = (forward ? nextWidget._getNextTabWidget() :
                                nextWidget._getPreviousTabWidget());

    } while(nextWidget && 
            (isc.EH.targetIsMasked(nextWidget, mask) || nextWidget.isDisabled() || 
             !nextWidget.isDrawn() || !nextWidget.isVisible()  || !nextWidget._canFocus()))
    if (nextWidget) {
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to:"+ nextWidget, "syntheticTabIndex");
        //<DEBUG
        
        
        nextWidget.focusAtEnd(forward)
        
    } else if (forward) {        
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to first widget", "syntheticTabIndex");
        //<DEBUG
        if (isc.EH._firstTabWidget == null || 
            // If we're the first widget in the synthetic tab order and we're non focusable
            // telling EH to drop focus into the first guy will cause EH to call this
            // method again, leading to a potential infinite loop.
            (isc.EH._firstTabWidget == this && 
             (this.isDisabled() || !this.isDrawn() || 
             !this.isVisible()  || !this._canFocus() || this.isMasked(mask)))) 
         {
             return;
         }
        isc.EH._focusInFirstWidget(mask);
    } else {
        //>DEBUG
        this.logInfo("focusInNextTabElement() shifting focus to last widget", "syntheticTabIndex");
        //<DEBUG
        if (isc.EH._lastTabWidget == null || 
            (isc.EH._lastTabWidget == this && 
             (this.isDisabled() || !this.isDrawn() || 
             !this.isVisible()  || !this._canFocus() || this.isMasked(mask))))
        {
            return;
        }
        isc.EH._focusInLastWidget(mask);
    }
},

// Helper - if we've got an autoAllocated tabIndex, ensure our children show up after this 
// widget in the page's tab order.

_slotChildrenIntoTabOrder : function () {

    
    var children = isc.isA.Layout(this) ? this.members : this.children;
    if (!children || children.length == 0) return;
    
    
    var afterChild = this._getNextTabWidget();
    for (var i = children.length -1; i >= 0; i--) {
        // support sparse arrays
        if (children[i] == null || (children[i].tabIndex != null && !children[i]._autoTabIndex)) continue;
        // Catch the case where we are the last auto-allocated tab widget
        if (afterChild == null) children[i]._setTabAfter(this);
        else children[i]._setTabBefore(afterChild);
        afterChild = children[i];
    }
},

// Helper to get the last descendant of a widget with auto-assigned tab index so
// setTabAfter() can reliably put the widget after the prev-widget's descendants.
// Currently only used in Layouts.
_getLastAutoIndexDescendant : function () {
    var children = this.children;
    
    if (isc.Layout && isc.isA.Layout(this)) children = this.members;
    if (children != null) {
        for (var i = children.length -1; i >= 0; i--) {
            var descendant = children[i]._getLastAutoIndexDescendant();
            if (descendant != null) return descendant;
        }
    }
    
    // If we're still going, we didn't find any descendants with auto-assigned TI.
    // return ourselves if appropriate
    if (this.tabIndex == null || this._autoTabIndex) return this;
    return null;
},


// Helper function to update the pointers to the next and previous widget in the page's tab order
// when we stop managing a widget's tab position.
// This can happen if the widget's tab position is specified explicitly via 'setTabIndex()', or if
// the widget becomes un-focusable.
// This function doesn't remove the widget from the tab order of the page - it doesn't set the
// native tab index to -1 or null.
// It just 
_removeFromAutoTabOrder : function () {
    // if we're not managing the tab index, nothing to do
    if (!this._autoTabIndex || !this.tabIndex) return;
    
    var prevWidget = this._getPreviousTabWidget(),
        nextWidget = this._getNextTabWidget();
        
    // if this method has been run before, we can just return
    if (prevWidget == null && nextWidget == null && isc.EH._lastTabWidget != this &&
        isc.EH._firstTabWidget != this) return;
        

    if (prevWidget) {
        prevWidget._setNextTabWidget(nextWidget);
    } else {
        isc.EH._firstTabWidget = nextWidget;
    }
    
    if (nextWidget) {
        nextWidget._setPreviousTabWidget(prevWidget);
    } else {
        
        isc.EH._lastTabWidget = prevWidget;
    }

    this._setPreviousTabWidget(null);
    this._setNextTabWidget(null);
},

// zIndex (stacking order)
// --------------------------------------------------------------------------------------------
// See also class method Canvas.getNextZIndex();

//>	@method	canvas.getZIndex()	(A)
//	Get the z-Index of this canvas.<br><br>
//
//		@group	zIndex
//
//      @param resovleToNumber(boolean)     
//              If passed <code>true</code>, for undrawn widgets, resolve "auto" to the next available zIndex.
//
//		@return	(number)	
//<
getZIndex : function (resolveToNumber) {

    
    if (!this.isDrawn() || isc.Browser.isSafari) {
        // if passed the 'resolveToNumber' parameter, update the zIndex to be a valid number.
        if (resolveToNumber && this.zIndex == isc.Canvas.AUTO) {
            this.setZIndex(isc.Canvas.getNextZIndex());
        }
        return this.zIndex;
    }
    
    return parseInt(this.getStyleHandle().zIndex);
},

//>	@method	canvas.setZIndex()	(A)
//			set the z-Index of the canvas.
//		@group	zIndex
//		@param	newIndex		(number)	new zIndex to set to
//<

setZIndex :	function (newIndex) {
    var oldZIndex = this.zIndex;
    if (oldZIndex == newIndex) return;
    
    var hasNativeFocusInIE = false;

    // NOTE: In IE there is a native bug whereby if the handle of a canvas has native
    // focus, and we attempt to set the z-index, the widget will not have its z-index updated.
    // Fix this by natively blurring the canvas, and re-focussing at the end of the message
    // (without firing any focus/blur handlers)
    if (isc.Browser.isIE && this.hasFocus && this._useNativeTabIndex)
    {
        hasNativeFocusInIE = true;
        this.logDebug("blurring due to zIndex change", "nativeFocus");
        this._setFocusWithoutHandler(false);
    }

    // adjust zIndices of the backmask and other special peers.  Note - we do this first if
    // we're moving the widget back in the Z-order, and last if we're moving the widget forward
    // in the z-order, so the peers never pop in front
    
    if (newIndex < oldZIndex) this._adjustSpecialPeers(newIndex);

	this.zIndex = newIndex;
	if (this.isDrawn()) {

        // if using two DIVs, set zIndex for both DIVs that we draw
        if (this.useClipDiv) this.getHandle().style.zIndex = newIndex

        this.getStyleHandle().zIndex = newIndex;
    }

    if (newIndex > oldZIndex) this._adjustSpecialPeers(newIndex);

    // Keep custom scrollbars above us.
    if (this.hscrollbar) this.hscrollbar.moveAbove(this);
    if (this.vscrollbar) this.vscrollbar.moveAbove(this);

    //>CornerClips keep corner clips above us
    if (this.clipCorners) {
        var clips = this._cornerClips;
		if (clips.TL) clips.TL.moveAbove(this);
		if (clips.TR) clips.TR.moveAbove(this);
		if (clips.BL) clips.BL.moveAbove(this);
		if (clips.BR) clips.BR.moveAbove(this);
    }
    //<CornerClips

    if (hasNativeFocusInIE) {
        // refocus on a timeout if we've blurred.
        
        this.delayCall("_restoreFocus", [], 0);
    }
    
    this.zIndexChanged(oldZIndex, newIndex);
},

_adjustSpecialPeers : function (newIndex) {
    if (this._edgesAsPeer()) this._edgedCanvas.setZIndex(newIndex-1);
    if (this._backMask) this._backMask.setZIndex(newIndex-2);
    if (this._shadow) this._shadow.setZIndex(newIndex-3);
    if (this.modalMask) this.modalMask.setZIndex(newIndex-4);
},

// zIndexChanged - notification fired if our zIndex has changed.
// calls 'parentZIndexChanged()' on any descendants by default.
zIndexChanged : function (oldZIndex, newZIndex) {
    if (this.children) this.children.map("parentZIndexChanged");
},

// parentZIndexChanged - notification fired when an ancestor's zIndex has changed.
// recursively calls this same method on any descendants so the whole descendant-chain is
// notified of the ZIndex change.
parentZIndexChanged : function () {
    if (this.children) this.children.map("parentZIndexChanged");
},


//>	@method	canvas.bringToFront()   ([])
// Puts this widget at the top of the stacking order, so it appears in front of all other
// widgets in the same parent.
//      @visibility external
//		@group	zIndex
//      @example    layer
//<

bringToFront : function () {
    if (isc._traceMarkers) arguments.__this = this;

    isc.Canvas._BIG_Z_INDEX += 18;
	this.setZIndex(isc.Canvas._BIG_Z_INDEX);
	
    isc._unmaskOnBringToFront = true;
    this.unmask();
    isc._unmaskOnBringToFront = false;
},


//>	@method	canvas.sendToBack() ([])
// Puts this widget at the bottom of the stacking order, so it appears behind all other
// widgets in the same parent.
//      @visibility external
//		@group	zIndex
//      @example    layer
//<

sendToBack : function () {
	isc.Canvas._SMALL_Z_INDEX -= 18;
	this.setZIndex(isc.Canvas._SMALL_Z_INDEX);
},


//>	@method	canvas.moveAbove()  ([])
// Puts this widget just above the specified widget in the stacking order, so it appears in
// front of the specified widget if both widgets have the same parent.
//      @visibility external
//		@group	zIndex
//		@param	canvas		(Canvas or subclass)	canvas to move above
//      @example    layer
//<
moveAbove : function (canvas) {
    // if the other Canvas has "auto" because it hasn't drawn yet, assign it a zIndex now
    // Note - this method will always set the zIndex so that this widget is adjascent to the 
    // other widget (may lower the zIndex of this widget if it is already well above the other
    // widget).
    // Therefore we can't no-op if this widget is already above the other widget without a 
    // functional change.
    var z = canvas.getZIndex(true);
    this.setZIndex(z + 6);
},


//>	@method	canvas.moveBelow()
// Puts this widget just below the specified widget in the stacking order, so it appears
// behind the specified widget if both widgets have the same parent.
//      @visibility external
//		@group	zIndex
//		@param	canvas		(Canvas or subclass)	canvas to move below
//      @example    layer
//<
moveBelow : function (canvas) {
    // Note - this method will always set the zIndex so that this widget is adjascent to the 
    // other widget (may raise the zIndex of this widget if it is already well below the other
    // widget).
    // Therefore we can't no-op if this widget is already below the other widget without a 
    // functional change.    
    // if the other Canvas has "auto" because it hasn't drawn yet, assign it a zIndex now
    var z = canvas.getZIndex(true);
    this.setZIndex(z - 6);
},


// Setting / Getting HTML Content
// --------------------------------------------------------------------------------------------

//>	@method	canvas.getContents()
//      Returns the contents of a Canvas. The contents are an HTML string.
// @return (HTML) contents of this Canvas
// @visibility external
//<
getContents : function () {
	var contents = (isc.isA.Function(this.contents) ? this.contents() : this.contents);
	return this.dynamicContents ? 
            contents.evalDynamicString(this, this.dynamicContentsVars) : 
            contents;
},


//>	@method	canvas.setContents()
// Changes the contents of a widget to newContents, an HTML string.
// <P>
// When +link{canvas.dynamicContents,dynamicContents} is set, <code>setContents()</code> can
// also be called with no arguments to cause contents to be re-evaluated.
//
//  @param	[newContents]	(string)    an HTML string to be set as the contents of this widget
//  @visibility external
//  @example setContents
//<
setContents : function (newContents) {
    if (newContents != null) this.contents = newContents;
	this.markForRedraw("setContents");
},

// Loading HTML Content
// ---------------------------------------------------------------------------------------

containsIFrame : function () {
    return this.contentsURL != null && this.contentsType == "page";
},



//>	@method	canvas.getContentsURL()    ()
//      Returns the contentsURL of a widget.
//<
getContentsURL : function () {
	return this.contentsURL;
},

//>	@method	canvas.setContentsURL()
//	Change the contentsURL of the Canvas
//
//		@param	newURL	(string)
//<
setContentsURL : function (url, params) {
    // store new URL
	this.contentsURL = url;

    // support special prefixes, eg [APPFILES]
    url = isc.Page.getURL(url);
    // support params (NOTE: doc'd under HTMLFlow)
    var allParams = isc.addProperties({}, this.contentsURLParams, params),
    url = isc.rpc.addParamsToURL(url, allParams);

    if (!this.isDrawn()) return;
    
    if (this.containsIFrame()) {
        // Modify the src property on the IFRAME to move to the new URL.
        var urlHandle = this._getURLHandle();
        
        if (!urlHandle || !url) this.markForRedraw("setContentsURL");
        else urlHandle.src = url;
    }
},

// Miscellaneous styling setters
// --------------------------------------------------------------------------------------------

//>	@method	canvas.setBackgroundColor()
//			Sets the background color of this widget to newColor.
//		@group	appearance
//		@param	newColor		(string)	new color to set the widget's background to
//      @visibility external
//<
setBackgroundColor : function (newColor) {
    if (newColor) this.backgroundColor = newColor;
    if (this.isDrawn()) {
		return this.getStyleHandle().backgroundColor = newColor;
	}
},


//>	@method	canvas.setBackgroundImage() 
//			Sets the background to an image file given by newImage. This URL should be given as a
//          string relative to the image directory for the page (./images by default).
//		@group	appearance
//		@param	newImage	(string)	new URL (local to Page image directory) for background image
//      @visibility external
//<
setBackgroundImage : function (newImage) {
    if (newImage) this.backgroundImage = newImage;
    if (this.isDrawn()) {
		if (this.isDrawn()) this.getStyleHandle().backgroundImage = 'url(' + this.getImgURL(this.backgroundImage) + ')';
	}
},


//>	@method	canvas.setBorder()
// Set the CSS border of this component, as a CSS string including border-width,
// border-style, and/or color (eg "2px solid blue").
// <P>
// This property applies the same border to all four sides of this component.  Different
// per-side borders can be set in a CSS style and applied via +link{styleName}.
//
// @group appearance
// @param newBorder (string) new border to set to (eg: "2px solid black")
// @visibility external
//<
setBorder : function (newBorder) {	
    this._cachedBorderSize = null;

    if (newBorder != null && !isc.isA.String(newBorder)) {
        newBorder = this._convertBorderToString(newBorder);
    }
    // bail if newBorder is null
    if (newBorder == null) return;
    
    // Avoid a mysterious JS error in IE6 if someone passes in a string like
    // "2px solid gold;" rather than "2px solid gold"
    if (newBorder.endsWith(";")) newBorder = newBorder.slice(0,newBorder.length-1);
    
    this.border = newBorder;
    
    var styleHandle = this.getStyleHandle();
    // if we're undrawn, no need to continue
    if (!styleHandle) return;
    if (styleHandle.border != newBorder) {
        styleHandle.border = newBorder;
    }
    
    this.adjustOverflow("setBorder");
    
    this.innerSizeChanged("Border thickness changed");
},

// convert a non-string border into something usable, or drop it
_convertBorderToString : function (border) {
    var specifiedBorder = border;
    if (isc.isA.Number(border)) {
        border += "px solid";
    } else {
        border = null;
         //>DEBUG
         this.logWarn("this.border defined as " + specifiedBorder + 
                    ". This property should have a string value - dropping this attribute.");
         //<DEBUG
    }
   
    return border;
},

//>	@method	canvas.getBorder()
// Get the border for this canvas
//		@group	appearance
//		@return (string)    css border string (eg: "2px solid black")
//<
getBorder : function () {
    return this.border;
},


//>	@method	canvas.setOpacity() ([])
//			Sets the opacity for the widget to the newOpacity value. This newOpacity
//          value must be within the range of 0 (transparent) to 100 (opaque). <br>
//          In Internet Explorer, any other filter effects will be wiped out. 
//		@group	cues
//		@param	newOpacity	(number)	new opacity level
//      @visibility external
//      @example translucency
//<
//>Animation
// @param [animating] (boolean) passed if this setOpacity is part of an animated set opacity
//<Animation
setOpacity : function (newOpacity, animating, forceFilter) {
    //this.logWarn("setOpacity: " + newOpacity + ", animating: " + animating +
    //             this.getStackTrace()); 

    //>Animation
    if (!animating && this.fadeAnimation) this.finishAnimation("fade");
    //<Animation
    var oldOpacity = this.opacity;
    this.opacity = newOpacity;
    
    // ensure we null out the opacity setting when we go back to 100 (opaque), except for Moz
    // (see below).
    // In IE at least, this avoids issues where filters interact with each other unexpectedly,
    // specifically an Alpha filter on some parent will cause burn through issues with Gradient
    // or AlphaImageLoader filters that appear within table cells nested somewhere underneath
    if (this.opacity == 100 && !forceFilter && !(this.smoothFade && isc.Browser.isMoz)) this.opacity = null;
   
    if (this.isDrawn()) {
        if (isc.Browser.isMoz) {
            
            var opacity = (this.opacity != null) ? this.opacity / 100 : "";
            
            if (this.smoothFade && (opacity == 1 || this.opacity == null)) opacity = 0.9999;
            if (this._useMozOpacity) this.getStyleHandle().MozOpacity = opacity;
            else this.getStyleHandle().opacity = opacity;
            
        } else if (isc.Browser.isIE) {
            // Using proprietary Microsoft filters to achieve opacity
            this.getStyleHandle().filter = (this.opacity == null ? "" :
                    "progid:DXImageTransform.Microsoft.Alpha(opacity="+this.opacity+")");

            
            
        // Safari, Opera, other: CSS3 opacity
        } else {
            var opacity = (this.opacity != null) ? this.opacity / 100 : "";
            this.getStyleHandle().opacity = opacity;
        }            
	}
    
    this._setPeersOpacity(newOpacity, animating, forceFilter || newOpacity != null);

    if (isc.Browser.isIE && this.fixIEOpacity && this.children) {
        
        for (var i = 0; i < this.children.length; i++) {
            var child = this.children[i];
            if (child.opacity == null && (forceFilter || newOpacity != null)) {
                //this.logWarn("setting child: " + child + " to 100");
                child.setOpacity(100, animating, true);
            } else if (child.opacity == 100) {
                //this.logWarn("setting child: " + child + " to null");
                child.setOpacity(null);
            }
        }
    }

    this.opacityChanged(newOpacity, animating);
},

//>	@method	canvas.opacityChanged() ([])
//  Observable method called whenever a Canvas changes opacity.
// @param	newOpacity	(number)	new opacity level
//<
//>Animation
// @param [animating] (boolean) passed if this setOpacity is part of an animated set opacity
//<Animation
opacityChanged : function (newOpacity, animating) {},

// When opacity changes, update peers with new opacity
_setPeersOpacity : function (newOpacity, animating, forceFilter) {
    if (!this.peers) return;
    for (var i = 0; i < this.peers.length; i++) {
        if (this.peers[i]._setOpacityWithMaster) {
            this.peers[i].setOpacity(newOpacity, animating, forceFilter);
        } else if (this.peers[i] == this.edgedCanvas && this.edgeOpacity) {
            // If edgeOpacity is set, convert it to a percentage of the parents opacity
            var compOpacity = Math.round(this.opacity * (this.edgeOpacity * .01));                     
            this.peers[i].setOpacity(compOpacity, animating, forceFilter);
        }
    }
},

 

//>	@method	canvas.setPrompt()
// Set the prompt for this Canvas. If <code>this.showHover</code> is true this will be displayed
// in a hover for this canvas.
//		@group	cues
//<
setPrompt : function (prompt) {
    this.prompt = prompt;
    this.updateHover();
},

//>	@method	canvas.hilite()
//	@group	debug
//
//  Show a flashing border around this Canvas.  Intended for debugging purposes, this is what the
//  Log window uses to indicate the selected Canvas.
//<
hilite : function () {
    isc.Log.hiliteCanvas(this.ID);
},

// Cursor handling
// --------------------------------------------------------------------------------------------

//>	@method	canvas.setCursor()  ([])
//			Sets the cursor for this widget to cursor. See the cursor property
//          for possible values.
//      @visibility external
//		@group	cues
//		@param	newCursor	(Cursor)	new cursor
//<
setCursor : function (newCursor) {
    if (newCursor && newCursor != this.cursor) {
        this.cursor = newCursor;
        // Call updateCursor to show the new cursor if appropriate
        this._updateCursor();
    }
},

//>	@method	canvas._applyCursor()     (I)
//		@group	cues
//			Internal method - actually updates the HTML to show a new cursor
//		@param	newCursor	(Cursor)	new cursor
//<
_applyCursor : function (newCursor) {
    if (this.isDrawn()) {
        
        if (isc.Browser.isMoz && newCursor == "hand") newCursor = isc.Canvas.HAND;
        
        this._styleCursor = newCursor;
        
        this.getStyleHandle().cursor = newCursor;
        // In double-div browsers, set the cursor of the content div.
        
        if (this.useClipDiv) this.getHandle().style.cursor = newCursor;
        
        // If we are having events proxied to us, update the proxiers' cursors too, since 
        // the proxiers' should be treated essentially like an extension of this widget's handle
        if (this._proxiers) {
            for (var i = 0; i < this._proxiers.length; i++) {
                this._proxiers[i]._applyCursor(newCursor);
            }
        }  

        if (this.ns.EH._mouseMask && (this == this.ns.EH.getTarget())) {
            this.ns.EH._mouseMask.setCursor(newCursor);
        }			
    }
    // In opera, if the mouse is over this widget, the cursor change won't be picked up
    // unless we force a refresh of the HTML (or the user mouses off, then back on the widget).
    
    if (isc.Browser.isOpera && isc.EH.lastEvent.target == this) this.markForRedraw();
},

_updateCursor : function() {
    var currentCursor = this.getCurrentCursor();

    if (this._styleCursor == currentCursor) return;
    // apply the appropriate cursor to the Canvas
    
    this._applyCursor(currentCursor);
},


getCurrentCursor : function () {
    // If we don't have a special cursor, we need to show the original cursor.
    var currentCursor = this.cursor;    
   
    // If setNoDropIndicator has been called, show the "no-drop" cursor when the user
    // drags over this widget.
    // Takes presidence over other custom cursors (EG even if disabled we should show this
    // no-drop indicator when the user drags over us).
    
    if (isc.EH.dragging && this._noDropIndicatorSet && (isc.EH.dragMoveTarget != this)) {
        currentCursor = this.noDropCursor;
       
    // If we're disabled, let the disabled cursor show
    } else if (this.isDisabled()) currentCursor = this.disabledCursor;
   
    // Drag indicators
    else {
        // Edge drag resizing
        var edgeCursor;
        if (this.canDragResize && this.edgeCursorMap) {
            // if this Canvas is resizable and there's an edgeCursorMap
            // determine whether we're on the edge, and show the appropriate cursor
            var edge = this.getEventEdge();
            if (edge && this.edgeCursorMap[edge]) {
                currentCursor = this.edgeCursorMap[edge];
                edgeCursor = true;
            }
            //this.logWarn("over edge: " + edge + " with cursor: " + currentCursor);
        }
        // drag repositioning
        if (!edgeCursor && this.canDragReposition && this.dragRepositionCursor) {
            currentCursor = this.dragRepositionCursor;
        }
    }
    
    return currentCursor;
},


// Hover handling

//> @method canvas.getHoverTarget() (A)
// This method is fired when a user moves over this widget, and returns a pointer to the widget
// that should recieve a hover event if the user remains positioned over this canvas.
// Default implementation will return the first ancestor of this widget (or this widget itself)
// for which <code>canHover</code> is true.  If it encounters a parent for which canHover is
// explicitly set to false, the default implementation returns null.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//  @return (Canvas) Hover target for the current event (or null)
//<
getHoverTarget : function (event, eventInfo) {
    var target = this;
    while (target) {
        if (target.getCanHover() == null) {
            // If the target has a prompt specified this implies the developer wants hover
            // behavior to show the prompt on that target.
            if (target.prompt != null) return target;
            target = target.parentElement;
        } else if (target.getCanHover()) {
            return target;
        } else {
            return null;
        }
    }
    return null;
},

//> @method canvas.startHover() (A)
// Handler fired when the mouse goes over a valid hover target, or some other canvas which
// identifies this as the hover target.  Starts the hover timer to fire public hover handling
// methods on the hoverTarget.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//<
startHover : function (event) {
    isc.Hover.setAction(this, this._handleHover, null, this.hoverDelay);
},

//> @method canvas.stopHover() (A)
// Handler fired when the mouse leaves a hover target.
// Clears the hover timer to fire public hover handling methods on the target.
//  @group hovers
//  @visibility internal
//  @param event (event) Current mouse event.
//<

stopHover : function (event) {   
    isc.Hover.clear();  
},

// Internal method fired when the hover timer returns - will fire exposed hover handler methods.
_handleHover : function () {
    //!DONTCOMBINE
    var EH = isc.EH,
        lastMoveTarget = EH.lastMoveTarget;     
    // Catch the case wher the user has moved out of this canvas
    
    var event = EH.lastEvent;
    if (!lastMoveTarget || lastMoveTarget.getHoverTarget(event) != this) return;
    
    return this.handleHover();
},

// getCanHover() - should this canvas fire hover events?
getCanHover : function () {
    return this.canHover;
},

//> @method canvas.handleHover() (A)
// Handler fired on a delay when the user hovers the mouse over this hover-target.
// Default implementation will fire <code>this.hover()</code> (if defined), and handle 
// showing the hover canvas if <code>this.showHover</code> is true.
//  @group hovers
//  @visibility external
//  @see canvas.canHover
//  @see canvas.showHover
//  @see canvas.hover()
//<
handleHover : function () {
    if (this.hover && this.hover() == false) return;
    if (this.showHover) {
        var HTML = this.getHoverHTML();
        if (HTML != null && !isc.isAn.emptyString(HTML)) {
            var hoverProperties = this._getHoverProperties();
            isc.Hover.show(HTML, hoverProperties, null, this);
        }
    }
},

//> @method canvas.updateHover() (A)
// If this canvas is currently showing a hover (see +link{canvas.handleHover}), this method
// can be called to update the contents of the hover. Has no effect if the hover canvas is not
// showing for this widget.
//  @param [hoverHTML] (string) Option to specify new HTML for the hover. If not passed, the result
//   of +link{canvas.getHoverHTML(),this.getHoverHTML()} will be used instead. Note that if the
//   hover HTML is empty the hover will be hidden.
//  @group hovers
//  @visibility external
//<
updateHover : function (hoverHTML) {
    if (isc.Hover.lastHoverCanvas != this || !isc.Hover.hoverCanvas.isVisible()) return;
    if (hoverHTML == null) hoverHTML = this.getHoverHTML();
    isc.Hover.show(hoverHTML,  this._getHoverProperties(), null, this);
},

//> @method canvas.hoverHidden() (A)
// If +link{canvas.showHover,showHover} is true for this canvas, this notification method will be
// fired whenever the hover shown in response to +link{canvas.handleHover(),handleHover()} is 
// hidden. This method may be observed or overridden.
// @group hovers
// @visibility external
//<
hoverHidden : function () {},

// Helper method to assemble the properties to apply to the hover canvas into an object to pass
// to Hover.show()
_getHoverProperties : function () {
    return {    width:this.hoverWidth, height:this.hoverHeight, align:this.hoverAlign,
                valign:this.hoverVAlign, baseStyle:this.hoverStyle, opacity:this.hoverOpacity,
                moveWithMouse:this.hoverMoveWithMouse, wrap:this.hoverWrap
           };
},

//> @method canvas.hover()
// If <code>canHover</code> is true for this widget, the <code>hover</code> string method will
// be fired when the user hovers over this canvas. If this method returns false, it will
// suppress the default behavior of showing a hover canvas if <code>this.showHover</code> 
// is true.
//  @group hovers
//  @see canvas.canHover
//  @visibility external
//<


//> @method canvas.getHoverHTML()
// If <code>this.showHover</code> is true, when the user holds the mouse over this Canvas for
// long enough to trigger a hover event, a hover canvas is shown by default. This method returns
// the contents of that hover canvas. Default implementation returns <code>this.prompt</code> -
// override for custom hover HTML. Note that returning <code>null</code> or an empty string will
// suppress the hover canvas altogether.
//  @group hovers
//  @see canvas.showHover
//  @visibility external
//<
getHoverHTML : function () {
    return this.prompt;
},

// CSS Style methods
// ------------------------------------------------------------------------------------------------------

//>	@method	canvas.setClassName()   ([A])
// Sets the CSS class for this widget
//      @visibility external
//		@group	appearance
//		@param	newClass		(string)	new CSS class name (must be defined previously)
//  @deprecated As of SmartClient version 5.5, use +link{canvas.setStyleName()} instead.
//<
_$styleName:"styleName",
setClassName : function (newClass) {
    // we expect this to happen a lot as we haven't converted all internal usage of .className
    // Log a warning at the info level only
    if (this.logIsInfoEnabled(this._$styleName)) {
        this.logInfo("call to deprecated setClassName() property - use setStyleName() instead");
    }
    return this.setStyleName(newClass);
},

//>	@method	canvas.setStyleName()   ([A])
// Sets the CSS class for this widget
//		@group	appearance
//		@param	newStyle (string)	new CSS class name (must be defined previously)
//      @visibility external
//      @example styles
//<
setStyleName : function (newStyle) {

    this._cachedBorderSize = null;
    this._cachedPadding = null;

    if (newStyle) {
        this.styleName = newStyle;
        // Also update the depreacted className property
        this.className = newStyle;
    }

    if (this.getClipHandle()) this.getClipHandle().className = this.styleName;

    
    if (this.overflow != isc.Canvas.HIDDEN) this.adjustOverflow("setStyleName");
    
},

//>	@method	canvas.getStateName()	(A)
//			get the CSS class for a particular canvas (not what it is, what it should be)
//			OVERRIDE to implement some other scheme
//		@group	appearance
//
//		@return	(CSSStyleName)	name of the style to set the canvas to
//<
getStateName : function () {
	var handleClassName = this.getClipHandle().className;
	
	return (handleClassName != null ? handleClassName : this.styleName);
},


// Context Menu Handling
// ------------------------------------------------------------------------------------------------

// Internal contextMenu event handler to fire partwise events if appropriate.
handleShowContextMenu : function (event) {
    if (event.target == this && this.useEventParts) {
        var partInfo = this.getEventPart(event);
        // Fire showCM for appropriate part
        if (partInfo.part) {
            if (this._firePartEvent(partInfo.part, 
                                    "showContextMenu", 
                                    partInfo.element,partInfo.ID,event) == false) return false;
        }
    }
    if (this.showContextMenu) return this.showContextMenu(event);

},

//>	@method	canvas.showContextMenu()	(A)
// Executed when the right mouse button is clicked.  The default implementation of
// this method auto-creates a +link{class:Menu} from the +link{attr:canvas.contextMenu} property on
// this component and then calls +link{method:menu.showContextMenu} on it to show it.
// <p>
// If you want to show a standard context menu, you can simply define your Menu and set it as the
// contextMenu property on your component - you do not need to override this method.
// <p>
// If you want to do some other processing before showing a menu or do something else entirely, then
// you should override this method.  Note that the return value from this method controls whether or
// not the native browser context menu is shown.
// 
// @return (boolean)	false == don't show native context menu, true == show native context menu
// @group widgetEvents
// @see attr:contextMenu
// @see method:menu.showContextMenu
// @see hideContextMenu()
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @platformNotes 
// On the Mac platform, context menu functionality may be triggered by <code>Command+click</code><br>
// On the Opera browser, context menu functionality may be triggered by <code>Shift+Ctrl+click</code>
//<
showContextMenu : function () {
    var menu = this.contextMenu;
	if (menu) {
		menu.target = this;
        if (!isc.isA.Canvas(menu)) {
            menu.autoDraw = false;
            this.contextMenu = menu = isc.Menu.create(menu);
        }
		menu.showContextMenu();
	}
	return (menu == null);
},


//>	@method	canvas.hideContextMenu()	(A)
//
// The default implementation of this method hides the contextMenu currently being shown for this
// component (which occurs when the mouse button that toggles the context menu is released).
// Override if you want some other behavior.	
//
// @see showContextMenu()
// @see menu.hideContextMenu()
// @group	widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<
hideContextMenu : function (){
	if (this.contextMenu) this.contextMenu.hideContextMenu();
},

// Mouse Events
// ------------------------------------------------------------------------------------------------------

//> @method canvas.mouseOver() (A)
//
// Executed when mouse enters this widget.  No default implementation.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.mouseDown() (A)
//
// Executed when the left mouse down is pressed on this widget.  No default implementation.
//
// @platformNotes If the end user system has only one mouse button, then it is considered the "left"
//                mouse button (and this method would execute when it is pressed on this widget).
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<

// _allowNativeTextSelection.
// By default returns this.canSelectText 

_allowNativeTextSelection : function (event) {
    return this.canSelectText;
},

//> @method canvas.rightMouseDown() (A)
//
// Executed when the right mouse down is pressed on this widget.  No default implementation.
//
// @platformNotes Some end user systems only have one mouse button.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.mouseStillDown() (A)
//
// Executed repeatedly (every +link{attr:canvas.mouseStillDownDelay} by default) when the system is idle -
// i.e. not busy running other scripts - and the left mouse button is held down after having been
// pressed in the object. This event is not native to JavaScript, but is provided by the ISC system.
// <p>
// Note: The event handling system waits +link{attr:canvas.mouseStillDownInitialDelay} before
// calling mouseStillDown for the first time on the widget.  Subsequently the method is called every
// +link{attr:canvas.mouseStillDownDelay}.  Both attributes are configurable per widget instance.
// <p>
// This method is called only when the left mouse is held down.
//
// @platformNotes Some end user systems only have one mouse button.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see mouseStillDownInitialDelay
// @see mouseStillDownDelay
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.mouseMove() (A)
//
// Executed when the mouse moves within this widget.  No default implementation.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.mouseOut() (A)
//
// Executed when the mouse leaves this widget.  No default implementation.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @visibility external
// @example customMouseEvents
//<



//> @method canvas.mouseUp() (A)
//
// Executed when the left mouse is released on this widget.  No default implementation.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customMouseEvents
//<


//> @method canvas.click() (A)
//
// Executed when the left mouse is clicked (pressed and then released) on this widget.  No default
// implementation.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.doubleClick() (A)
//
// Executed when the left mouse button is clicked twice in rapid succession (within
// +link{attr:Canvas.doubleClickDelay} by default) in this object.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @see doubleClickDelay
// @group widgetEvents
// @visibility external
//<

//> @method canvas.mouseWheel() (A)
//
// Executed when the mouse wheel is actuated.
//
// @platformNotes Not all end user systems have mouse wheels.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see EventHandler.getWheelDelta()
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customMouseEvents
//<

// Partwise mouse event handling
// ------------------------------------------------------------------------------------------------------
// In some cases we want to be able to respond to events over HTML elements written into our handle
// (For example the 'drawRect()' / 'rectMouseMove()' et al system).
// We handle this by writing an attribute 'eventPart' onto the elements in question and treating
// events occurring over these elements specially (firing custom handlers depending on the 
// part name).
// This is all disabled by default - enable by flipping the 'useEventParts' attribute

// Implement handleMouseMove to fire part-wise events if we're configured to do so and the
// user is over a 'part' type element
handleMouseMove : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) {
        var partInfo = this.getEventPart(event),
            lastOverPart = this._lastOverPart;

        // If we moved out of a part, fire a [part]Out handler
        if (lastOverPart &&  lastOverPart.part &&
            (lastOverPart.part != partInfo.part || lastOverPart.ID != partInfo.ID)) 
        {
            this._firePartEvent(lastOverPart.part, isc.EH.MOUSE_OUT, 
                                lastOverPart.element, lastOverPart.ID, event);
        }
        
        // Fire over or move handler on the new part
        if (partInfo.part) {
        
            var newPart = !lastOverPart || (lastOverPart.ID != partInfo.ID),
                eventType = (newPart ? isc.EH.MOUSE_OVER : isc.EH.MOUSE_MOVE)
            ;
            
            this._firePartEvent(partInfo.part, eventType, partInfo.element,partInfo.ID,event);
            
            // If this is a new part, we want to start the part-wise hover timer
            
            if (newPart) {
                isc.Hover.setAction(this, this._handleRectHover, [partInfo.element, partInfo.ID], this.hoverDelay);                
            }
            
        }
        
        this._lastOverPart = partInfo;
    }
    
    if (this.mouseMove) return this.mouseMove(event, eventInfo);
},

// Handle a hover on a rect written out by the drawRect() system
_handleRectHover : function (element, ID) {
    //!DONTCOMBINE
    if (this._lastOverPart) this._firePartEvent(this._lastOverPart.part, "hover", element, ID);    
},

// Implement handleMouseOut to trip the part-wise mouseOut handler if we're firing
// partwise events, and the user is moving off an event part.
handleMouseOut : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) {
        var lastOverPart = this._lastOverPart;
        if (lastOverPart && lastOverPart.part) {
            this._firePartEvent(lastOverPart.part, isc.EH.MOUSE_OUT, 
                                lastOverPart.element, lastOverPart.ID, event);
        }
    }
    if (this.mouseOut) return this.mouseOut(event, eventInfo);
},

// Implement handle mouseDown, mouseUp, click and doubleClick to fire partwise events if
// appropriate
handleMouseDown : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.MOUSE_DOWN);
    if (this.mouseDown) return this.mouseDown(event, eventInfo);
},

handleMouseUp : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.MOUSE_UP);
    if (this.mouseUp) return this.mouseUp(event, eventInfo);

},

handleClick : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.CLICK);
    if (this.click) return this.click(event, eventInfo);
},

handleDoubleClick : function (event, eventInfo) {
    if (event.target == this && this.useEventParts) this.firePartEvent(event, isc.EH.DOUBLE_CLICK);
    if (this.doubleClick) return this.doubleClick(event, eventInfo);
},

_$eventPart:"eventpart",
//> @method canvas.getEventPart()
// If this canvas is using partwise events, given an event determine which part it occurred over
// @visibility eventParts
//<
getEventPart : function (event) {
    if (!event) event = isc.EH.lastEvent;
    var element = event.nativeTarget;
    return this.getElementPart(element);
        
},

getElementPart : function (element) {
    var part, partID;
    
    if (element && element.getAttribute) part = element.getAttribute(this._$eventPart);
    
    if (part && part != isc.emptyString) {
        var elementID = element.id;
        if (elementID && elementID != isc.emptyString) {
            // part elements' IDs are expected to be of the form <widgetID>_partType_partID
            partID = elementID.substring(this.getID().length + part.length + 2);
        }
    }
    
    return {part:part, ID:partID, element:element};
},

// For the AutoTest APIs, we need to be able to get back at the eventPart handle from the part
// name.

getPartElement : function (partObj) {
    // check for standardized element ID first
    var part = partObj.part,
        partID = partObj.partID,
        elementID = this.getID + "_" + part;
    if (partID) elementID += partID;
    var element = isc.Element.get(elementID);
    if (element) return element;
    
    // If we didn't find it do an iteration through our descendent nodes
    return isc.Element.findAttribute(this.getHandle(), this._$eventPart, part);
},

// Given a generic event on this widget, determine whether it occurred over a specific 'part'
// If so, fire the appropriate part event.
firePartEvent : function (event, eventType) {
    if (!this.useEventParts || !event) return;
    var partInfo = this.getEventPart(event);
    if (!partInfo.part) return;
    
    if (!eventType) eventType = event.eventType;
    
    return this._firePartEvent(partInfo.part, eventType, partInfo.element, partInfo.ID, event);
},

// _firePartEvent() - helper to fire <partName>MouseOver() et al.
_firePartEvent : function (partName, eventType, element,ID,event) {
    var handlerName = this.getPartEventHandler(partName, eventType);

    if (this[handlerName]) {
        return this[handlerName](element,ID,event);
    }
},

//> @method canvas.getPartEventHandler()
// Given a part name and an event type, this method returns the name of the part-wise event 
// handler to fire (such as "rectMouseOut")
// @visibility eventParts
//<
getPartEventHandler : function (partName, event) {

    if (!isc.Canvas._partHandlers[partName]) isc.Canvas._partHandlers[partName] = {};
    if (!isc.Canvas._partHandlers[partName][event]) {

        // We will fire [partName]MouseOver, [partName]Click, etc.
        var suffix = event.substring(0,1).toUpperCase() + event.substring(1);
        isc.Canvas._partHandlers[partName][event] = partName + suffix;
    }
    
   return isc.Canvas._partHandlers[partName][event];
},



// Drag and Drop
// ------------------------------------------------------------------------------------------------------

//> @method canvas.dragRepositionStart() (A)
//
// Executed when dragging first starts. No default implementation.  Create this handler to set
// things up for the drag reposition.
//
// @return (boolean) false to cancel the drag reposition action
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dragRepositionMove() (A)
//
// Executed every time the mouse moves while drag-repositioning. If this method
// does not return false, the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline" will automatically be moved as appropriate
// whenever the mouse moves.
//
// @return (boolean) false to suppress auto-move of the +link{attr:canvas.dragTarget} or outline.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dragRepositionStop() (A)
//
// Executed when the mouse button is released at the end of the drag. Your
// widget can use this opportunity to fire custom code based upon where the
// mouse button was released, etc. 
// <p>
// Returning true from this handler will cause the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") to be left in its current
// location. Returning false from this handler will cause it to snap back to its
// original location.
//
// @return (boolean) false to snap the +link{attr:canvas.dragTarget} (or outline) back to its
//                   original location or true to leave it at the current cursor position.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<

//> @method canvas.dragResizeStart() (A)
//
// Executed when resize dragging first starts. No default implementation.  
// Create this handler to set things up for the drag resize.
//
// @return (boolean) false to cancel the drag reposition action
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dragResizeMove() (A)
//
// Executed every time the mouse moves while drag-resizing. If this method
// does not return false, the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline" will automatically be moved as appropriate
// whenever the mouse moves.
//
// @return (boolean) false to suppress auto-resize of the +link{attr:canvas.dragTarget} or outline.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dragResizeStop() (A)
//
// Executed when the mouse button is released at the end of the drag resize. Your
// widget can use this opportunity to fire custom code based upon where the
// mouse button was released, etc. 
// <p>
// Returning true from this handler will cause the +link{attr:canvas.dragTarget} (or outline if
// +link{attr:canvas.dragAppearance} is set to "outline") to be left at its current size. Returning
// false from this handler will cause it to snap back to its original location size
//
// @return (boolean) false to snap the +link{attr:canvas.dragTarget} (or outline) back to its
//                   original size or true to leave it at the current cursor position.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dragStart() (A)
// Executed when dragging first starts. Your widget can use this opportunity to set
// things up for the drag, such as setting the drag tracker. Returning false from this
// event handler will cancel the drag action entirely.
// <p>
// A drag action is considered to be begin when the mouse has moved
// +link{attr:canvas.dragStartDistance} with the left mouse down.
//
// @return (boolean) false to cancel drag action.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example dragPan
//<


//> @method canvas.dragMove() (A)
// Executed every time the mouse moves while dragging this canvas.
//
// @return (boolean) false to cancel drag interaction.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example dragPan
//<


//> @method canvas.dragStop() (A)
// Executed when the mouse button is released at the end of the drag. Your widget can
// use this opportunity to fire code based on the last location of the drag or reset any
// visual state that was sent.
//
// @return (boolean) false to cancel drag interaction.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
//<


//> @method canvas.dropOver() (A)
//
// Executed when the compatible dragged object is first moved over this drop target. Your
// implementation can use this to show a custom visual indication that the object can be
// dropped here.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customDrag
//<

//> @method canvas.dropMove() (A)
//
// Executed whenever the compatible dragged object is moved over this drop target. You
// can use this to show a custom visual indication of where the drop would occur within the
// widget.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customDrag
//<


//> @method canvas.dropOut() (A)
//
// Executed when the dragged object is moved out of the rectangle of this drop target. If you
// have set a visual indication in dropOver or dropMove, you should reset it to its normal
// state in dropOut.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @visibility external
// @example customDrag
//<



//> @method canvas.drop() (A)
//
// Executed when the mouse button is released over a compatible drop target at the end of
// a drag sequence. Your widget should implement whatever it wants to do when receiving a
// drop here. For example, in a file moving interface, a drop might mean that you should
// move or copy the dragged file into the folder it was dropped on, or dropping something in
// a trash can might mean to clear it from the screen.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
//
// @group widgetEvents
// @see getOffsetX()
// @see getOffsetY()
// @see EventHandler.getDragTarget()
//
// @visibility external
// @example dragCreate
//<


// Keyboard handling
// ------------------------------------------------------------------------------------------------------


//> @method canvas.keyDown() (A)
//
// Executed when a key is pressed on a focusable widget (+link{attr:canvas.canFocus}: true). 
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
//<


//> @method canvas.keyUp() (A)
//
// Executed when a key is released on a focusable widget (+link{attr:canvas.canFocus}: true). 
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (boolean) false to prevent this event from bubbling to this widget's parent, true or
// undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
//<


//> @method canvas.keyPress() (A)
//
// Executed when a key is pressed and released on a focusable widget (+link{attr:canvas.canFocus}:
// true). 
// <P>
// Use +link{EventHandler.getKey()} to find out the +link{type:KeyName,keyName} of the key that
// was pressed, and use +link{EventHandler.shiftKeyDown()} and related functions to determine
// whether modifier keys were down.
//
// @return (boolean) false to suppress native behavior in response to the keyPress, and prevent 
//                   this event from bubbling to this widget's parent, or true or undefined to bubble.
// @group widgetEvents
// @see canFocus
// @visibility external
// @example keyboardEvents
//<



//>	@method	canvas.getDragType()	(A)
// Return the type of stuff that was dragged from this object
//
//		@return	(DragTypes)	
// @group dragdrop
//<
getDragType : function () {
	return this.dragType;
},


//>	@method	canvas.willAcceptDrop()	[A]
//
// Returns true if the widget object being dragged can be dropped on this widget, and
// false otherwise.  The default implementation of this method simply compares the
// +link{Canvas.dragType} of the <code>dragTarget</code> (the component being dragged from)
// with the list of +link{Canvas.dropTypes} on this Canvas.  If the +link{Canvas.dropTypes}
// list contains the +link{Canvas.dragType} value, then this method returns true.  Otherwise it
// returns false.
//
// @return	(boolean)	true if the widget object being dragged can be dropped on this widget,
//                      false otherwise
//
// @see Canvas.dragType    
// @see Canvas.dropTypes
// @see Canvas.dragTarget
// @see Canvas.drop
//
// @group dragdrop
// @visibility external
//<
willAcceptDrop : function () {

    // if nothing is currently being dragged, return false
    if (this.ns.EH.dragTarget == null) return false;
    
	// if the dropTypes of this object is not set, 
	// 	assume we can take anything...
	if (this.dropTypes == isc.Canvas.ANYTHING || this.dropTypes == null || 
        isc.is.emptyString(this.dropTypes)) 
    {
        return true;
    }

	// get the type of stuff that's being dragged
	var type = this.ns.EH.dragTarget.getDragType();
	// if the object being dragged has no dragType, assume we can't take it
	if (type == null || isc.is.emptyString(type)) return false;

	// otherwise go based on the type of the drag types
	if (isc.isA.String(type)) {
		// if it's a string, return true if our dropTypes contains the type
		return this.dropTypes.contains(type);
	} else if (isc.isAn.Array(type)) {
		// if it's an array, return true if our dropTypes contains all sub-types
		for (var i = 0, OK = true, length = type.length; i < length  && OK; i++) {
			OK = OK && (this.dropTypes.contains(type));
		}
		return OK;
	}
	
	// otherwise assume we can't take it
	return false;
},


_showDragMask : function () { 
    // show() the eventMask canvas if it's hidden
    // Note: check _eventMask.visibility rather than eventMask.isVisible() because of the
    // case where a parent is hidden
    if (this._eventMask.visibility == isc.Canvas.HIDDEN) this._eventMask.show();
},

_hideDragMask : function () { 
    // Note: check _eventMask.visibility rather than eventMask.isVisible() because of the
    // case where a parent is hidden
    if (this._eventMask.visibility != isc.Canvas.HIDDEN) this._eventMask.hide();
},

// handleDrop() -- if 'onDrop' exists fire this before the standard drop behavior

handleDrop : function (event,eventInfo) {
    if (this.onDrop != null && (this.onDrop() == false)) return false;
    return this.drop(event,eventInfo);
},



// Drag/drop snap-to-grid functionality

//>	@method	canvas.getHSnapPosition()	(A)

// Override this method to provide a custom snap-to grid.  Note that you do not need to do
// this if your grid is regular (ie, grid points are every x pixels); regular grids should be 
// defined using +link{canvas.snapHGap} and +link{canvas.snapVGap}.
// You should only override this method if you want to provide support for a grid of 
// irregularly-placed points
//
// @param coordinate (integer) x-coordinate of the drag event relative to the inside of this widget
// @param [direction] (string) "before" or "after" denoting whether the returned coordinate should
//   match the left or right edge of the current square. If unset +link{canvas.snapHDirection} will
//   be used by default
// @return (number) The horizontal coordinate to snap to
// @group dragdrop
// @visibility external
//<
getHSnapPosition : function (coordinate, direction) {
    if (! direction) {
        direction = this.snapHDirection;
    }  
    if (direction != isc.Canvas.BEFORE && 
        direction != isc.Canvas.AFTER  &&
        direction != isc.Canvas.NEAREST) {
        // log an error and return the supplied coord
        return coordinate;
    }
    
    var before = Math.floor(coordinate / this.snapHGap) * this.snapHGap;
    var after  = before + this.snapHGap;
    var halfway = before + this.snapHGap / 2;
    
    if (direction == isc.Canvas.BEFORE) {
        return before;
    } else if (direction == isc.Canvas.AFTER) {
        return after;
    } else {
        // If we're exactly inbetween, go left
        if (coordinate <= halfway) return before;
        else return after;
    }
    
},

//>	@method	canvas.getVSnapPosition()	(A)
// Override this method to provide a custom snap-to grid.  Note that you do not need to do
// this if your grid is regular (ie, grid points are every x pixels) - regular grids should be 
// defined using +link{canvas.snapHGap} and +link{canvas.snapVGap}.
// You should only override this method if you want to provide support for a grid of 
// irregularly-placed points
//
// @param coordinate (integer) y-coordinate of the drag event relative to the inside of this widget
// @param [direction] (string) "before" or "after" denoting whether the returned coordinate should
//   match the top or bottom edge of the current square. If unset +link{canvas.snapHDirection} will
//   be used by default
// @visibility external
//  @group dragdrop
//  @return (number) The vertical coordinate to snap to
//<
getVSnapPosition : function (coordinate, direction) {
    if (! direction) {
        direction = this.snapVDirection;
    }  
    if (direction != isc.Canvas.BEFORE && 
        direction != isc.Canvas.AFTER  &&
        direction != isc.Canvas.NEAREST) {
        // log an error and return the supplied coord
        return coordinate;
    }
    
    var before = Math.floor(coordinate/ this.snapVGap) * this.snapVGap;
    
    var after  = before + this.snapVGap;
    var halfway = before + this.snapVGap / 2;
    
    if (direction == isc.Canvas.BEFORE) {
        return before;
    } else if (direction == isc.Canvas.AFTER) {
        return after;
    } else {
        // If we're exactly inbetween, go up
        if (coordinate <= halfway) return before;
        else return after;
    }
},

//>	@method	canvas.shouldSnapOnDrop()	(A)
// Override this method to give programmatic control over whether or not the parameter 
// <code>dragTarget</code> should snap to this object's grid when dropped.  Note that this only applies
// if snap-to-grid is enabled on either <code>dragTarget</code> or this object.  See 
// +link{canvas.snapToGrid} and +link{canvas.childrenSnapToGrid}.
// <P>
// The default implementation simply returns true.
//
// @visibility external
// @group dragdrop
// @param dragTarget (isc.Canvas) The object about to be dropped
// @return (boolean) true if <code>dragTarget</code> should snap to this object's grid; otherwise false
//<
shouldSnapOnDrop : function (dragTarget) {
    return true;
},

// internal helper to suppress drag offsets when dragging child in snapToGrid mode
noSnapDragOffset : function (dragTarget) {
    return false;
},


// Images
// ------------------------------------------------------------------------------------------------------

//>	@method	canvas.setAppImgDir()
// Set the image directory for this individual widget.
// @group images
// @param	[URL]	(URL)	URL (relative to Page.appImgDir) for where images for this widget live
//<
setAppImgDir : function (URL) {
	if (URL) this.appImgDir = URL;
},

//>	@method	canvas.getAppImgDir()
// Return the image directory for this individual widget, prepended with the Page image directory.
// @group images
// @return	(URL)	Image directory (including Page image directory) for this widget.
//<
getAppImgDir : function () {
	return isc.Page.getImgURL("", this.appImgDir);
},

//>	@method	canvas.setSkinImgDir()
// Set the widget image directory for this individual widget.
// @group images
// @param	[URL]	(URL)	URL (relative to Page.appImgDir) for where images for this widget live
//<
setSkinImgDir : function (URL) {
	if (URL) this.skinImgDir = URL;
},

//>	@method	canvas.getSkinImgDir()
// Return the widget image directory for this individual widget, prepended with the Page image
// directory.
//
// @group images
// @return	(URL)	Widget mage directory (including Page widget image directory) for this widget.
//<
getSkinImgDir : function () {
	return isc.Page.getSkinImgDir(this.skinImgDir);
},


//>	@method	Canvas.getImgURL() (A)
// Return the full URL for an image to be drawn in this canvas.
// <P>
// If the passed URL begins with the special prefix "[SKIN]", it will have the
// widget.skinImgDir and Page.skinImgDir prepended.  Otherwise the image is assumed to be
// application-specific, and will have the widget.appImgDir and Page.appImgDir automatically
// prepended.
// <P>
// Note that if passed an absolute path (starting with "/" or "http://" for example), no extra
// image directory information will be prepended to the generated URL.// 
//		
// @param URL      (string) URL local to skin or application image directory
// @param [imgDir] (string) optional image directory to override the default for this Canvas
// @group images
// @return (string) URL to use
// @visibility external
//<
getImgURL : function (src, imgDir) {
    return isc.Canvas.getImgURL(src, imgDir, this);
},


//> @object ImgProperties
// 
// A set of properties that can be used to create an image.
//
// @treeLocation Client Reference/Foundation/Canvas
// @visibility external
//<

//> @attr imgProperties.src (URL : null : IRW)
//
// Specifies the URL of the image local to the skin or application directory.
//
// @visibility external 
//<    

//> @attr imgProperties.width (number : null : IRW)
//
// Specifies the width of the image.
//
// @visibility external 
//<    

//> @attr imgProperties.height (number : null : IRW)
//
// Specifies the height of the image.
//
// @visibility external 
//<    

//> @attr imgProperties.name (string : null : IRW)
//
// Specifies the name of the image. This is an identifier unique to the canvas, and subsequent
// calls to <code>+link{method:canvas.getImage()}</code> and
// <code>+link{method:canvas.setImage()}</code> 
// with this name will act on the image object created using this ImgProperties object.
//
// @visibility external 
//<    

//> @attr imgProperties.extraStuff (string : null : IRW)
//
// Specifies the additional attributes to write in the tag.
//
// @visibility external 
//<    

//> @attr imgProperties.imgDir (URL : null : IRW)
//
// Specifies the image-specific image directory to override the default.
//
// @visibility external 
//<    

//>	@method canvas.imgHTML() (A)
// Generates the HTML for an image unique to this Canvas.
// <P>
// The full URL for the image will be formed according to the rules documented for
// <code>+link{method:canvas.getImgURL()}</code>.
// <P>
// The created image will have an identifier unique to this Canvas, and subsequent calls to
// <code>+link{method:canvas.getImage()}</code> and
// <code>+link{method:canvas.setImage()}</code> 
// with the name passed to this function will act on the image object produced by the HTML
// returned from this call.
//
// @param src           (SCImgURL)	URL local to the skin or application directory.<br>
//		NOTE: instead of passing several parameters, you can pass an object as the 'src'
//      parameter with properties for all the various function parameters with, eg:<br>
//      canvas.imgHTML( {src:"foo", width:10, height:10} );
//
// @param [width]       (number)	width of the image
// @param [height]      (number)	height of the image
// @param [name]        (string)	name for the image
// @param [extraStuff]  (string)	additional attributes to write in the tag
// @param [imgDir]      (string)	image-specific image directory to override the default
//                                  for this Canvas
// @return	(string)				HTML to draw the image.
//
// @group images
// @visibility external
//<
imgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML) {
    return isc.Canvas.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML, this);
},

// returns an imgHTML template that contains an open slot for a unique name attribute
// for the image.  Used in inner loops.
_getImgHTMLTemplate : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML) {
    return isc.Canvas.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML, this, true);
},

//>	@method	canvas.getImage() (A)
// Retrieve a native image element by name.
// <P>
// The image element must have been created from HTML generated by calling
// <code>canvas.imgHTML()</code> on this particular Canvas.
// 
// @param	identifier (string)	name of the image to get, as originally passed to
//                              <code>imgHTML</code>
// @return	(object)	DOM image object if found, else null
// @group images
// @visibility external
//<
getImage : function (identifier) {
	// if identifier is a string, prepend the canvas name
	if (isc.isA.String(identifier)) identifier = this.getCanvasName() + identifier;

	// if we've been drawn and there is a document object to query
    var handle = this.getHandle();
	if (handle) {
        if (isc.Page.isXHTML()) {
            
            return document.getElementById(identifier);
        } else {
        
            if (handle.document) {
                // just ask that for the image
                return handle.document.images[identifier];
            } else {
                
                return document.images[identifier];
            }
        }
    }

	// not found -- return null;
	return null;
},


//>	@method	canvas.setImage() (A)
// Set the URL of an image element by name.
// <p>
// The image element must have been created from HTML generated by calling
// <code>canvas.imgHTML()</code> on this particular Canvas.
//
// @param identifier (string)	name of the image to change, as originally passed to
//                              <code>imgHTML</code>
// @param URL		 (SCImgURL)	URL for the image
// @param [imgDir]	 (string)	optional image directory, overrides the default for this Canvas
// @group images
// @visibility external
//<
setImage : function (identifier, src, imgDir) {
	// get the image
	var image = this.getImage(identifier);

    if (image == null) {
        //>DEBUG
        this.logWarn("setImage: image '" + identifier + "' couldn't be found");
        //<DEBUG
        return;
    }

    isc.Canvas._setImageURL(image, src, imgDir, this);
},

//>	@method	canvas.linkHTML() (A)
// Generates the HTML for a standard link element
//
// @param href  (string)	URL for the link to point to
// @param [text]  (string)    Text to for the link (defaults to the href)
// @param [target] (string)   Target window for the link (defaults to opening in a new, unnamed window)
// @param [ID] (string)     optional ID for the link element to be written out
// @param [tabIndex] (number) optional tabIndex for the link
// @param [accessKey] (string) optional accessKey for the link
// @return	(string)        HTML for the link
//
// @visibility external
//<
// Additional 'extrastuff' param 
linkHTML : function (href, text, target, ID, tabIndex, accessKey, extraStuff) {
    return isc.Canvas.linkHTML(href,text,target, ID, tabIndex, accessKey, extraStuff);
},

// ----------------------------------------------------------------------------------------

//>	@method	canvas.inWhichPosition()	(A)
//			given a list of numerical coordinates and a single coordinate,
//			return which item the coordinate falls in
//
//		@group	utils
//
//		@param	list			(array of numbers)	these are sizes (widths, heights, etc) of each
//		                         item, such as that returned by Canvas.applyStretchResizePolicy()
//		@param	coord			(number)	coordinate, such as an x or y coordinate from an Event
//		@param	[direction]	    (Page.LTR or Page.RTL)	direction 
//									-- if LTR we scan from left to right, if RTL we scan from right
//									   to left unspecified == LTR
//		@return	(number)	
//				-1 = before beginning of list
//				-2 = after end of list
//				 #  = in that position
//<
inWhichPosition : function (list, coord, textDirection) {
    //this.logWarn("inWhichPosition: coord: " + coord + "\nlist: " + list);
    
    // if we're before the first item, return -1
    if (!list || coord < 0) return -1;
    
    // iterate through the list of sizes, returning the one containing the coord
    if (textDirection == isc.Page.RTL) {
        // direction right to left: coord is still an offset from left, but assume list of
        // lengths is laid out right to left
        var totalSize = list.sum();
        for (var c = 0, numCols = list.length; c < numCols; c++) {
            if (coord >= totalSize-list[c]) return c;
            totalSize -= list[c];
        }
    } else {
        for (var c = 0, numCols = list.length; c < numCols; c++) {
            if (coord <= list[c]) {
                return c;
            }
            coord -= list[c];
        }
    }
    // not found -- return -2
    return -2;
},

// Log window: update stats
// --------------------------------------------------------------------------------------------

// add or remove from canvasList
_$count : "count",
_canvasList : function (add) {
    var list = isc.Canvas._canvasList;
    if (add) list.add(this);
    else list.remove(this);
    //>DEBUG
    if (this._iscInternal) {
        isc.Canvas._iscInternalCount += (add ? 1 : -1);
    } else {
        isc.Log.updateStats(this._$count);
    } //<DEBUG
},

//>DEBUG increment some stat we're drawing an update the log window
_addStat : function (stat) {
    if (!this._iscInternal) {
        isc.Canvas._stats[stat]++;
        isc.Log.updateStats(stat);
    }
}, //<DEBUG


// Attached Peers
// ----------------------------------------------------------------------------------------


_attachedPeers : function (side) {
    var attachedPeers = this._attachedPeerMap;
    if (!attachedPeers) return null;
    if (side) return attachedPeers[side];
},

_registerAttachedPeer : function (peer, side, offset) {
    if (peer == null || side == null) return;
    if (!this._attachedPeerMap) this._attachedPeerMap = {};
    if (!this._attachedPeerMap[side]) this._attachedPeerMap[side] = [];
    this._attachedPeerMap[side].add(peer);
    
    if (offset != null) peer._attachedPeerOffset = offset
    delete this._cachedMargins;
    delete this._fullMargins;
},

_unRegisterAttachedPeer : function (peer, side, offset) {
    if (peer == null || side == null) return;
    if (!this._attachedPeerMap ||!this._attachedPeerMap[side]) return;
    this._attachedPeerMap[side].remove(peer);
    delete peer._attachedPeerOffset;
    delete this._cachedMargins;
    delete this._fullMargins;    
},

// -----------------------------------------------------------------------------------------





//>CornerClips
// make corner cap elements for subtractive rounded corners
_makeCornerClips : function () {
	this._cornerClips = {};
	for (var i = 0; i < this.clippedCorners.length; i++) {
		this._makeCornerClip(this.clippedCorners[i])
	}
},

// make a single corner cap element	
// could convert position param to a cap properties obj for more flexibility here
// (if we do, note that helper methods currently assume position is a string)
// should check for valid positions here
_makeCornerClip : function (position) {
	var clips = this._cornerClips,
		capLeft = this.left,
		capTop = this.top,
		capWidth = this.cornerClipWidth || this.cornerClipSize,
		capHeight = this.cornerClipHeight || this.cornerClipSize;

	// calculate position for this corner
	if (position == "TR" || position == "BR") {
		capLeft = capLeft + this.getWidth() - capWidth;
	}
	if (position == "BL" || position == "BR") {
		capTop = capTop + this.getHeight() - capHeight;
	}

    // we can only do no-image corner masks in IE5.5+
    if (this.noCornerClipImages && !(isc.Browser.isIE && isc.Browser.minorVersion >= 5.5)) {
        this.noCornerClipImages = false;
    }

	var newCap = clips[position] = isc.ClassFactory.newInstance({
		_constructor:(this.noCornerClipImages ? "Canvas" : "Img"),
		left:capLeft,
		top:capTop,
		width:capWidth,
		height:capHeight,
		eventProxy:this,
        //border:"1px solid blue",
		src:(this.noCornerClipImages ? null : this._getCornerImage(position)),
        
		contents:(this.noCornerClipImages ? 
            this._getCornerHTML(capWidth, capHeight, position) : null)

	}, this._cornerProperties);
		
	this.addPeer(newCap);
	newCap.moveAbove(this);
},
	
_finishCornerClips : function () {
    if (!this.noCornerClipImages) return;
    for (var edge in this._cornerClips) {
        var cap = this._cornerClips[edge],
            div = cap.getHandle().firstChild,
            style = div.style;
        //this.logWarn("edge: " + edge + ", cap div: " + this.echo(div));

        // kick in irising transition
        div.filters[0].apply();
        // transition to hidden version (to create transparent space)
        style.visibility = "hidden";
        // move transition to 71% completion (where circle touches edge)
        div.filters[0].percent=71;
    }
},

// generate image filename
// jumping directly to Img.urlForState() here; is there a better approach?
// currently using position and color only; could enhance with size if scaling is unacceptable
_getCornerImage : function (position) {
	return isc.Img.urlForState(
		this.cornerClipImage,
		null, // selected is unused
        null, // as is focused
		this.cornerClipColor, // embed hex color code in the file name
		position);
},
	
_getCornerHTML : function (capWidth, capHeight, edge) {

    var output = isc.SB.create();
 
       

    // containing DIV: 4x the area of the final cap, since we want to show only 1/4 of frozen
    // transition per cap
    output.append(
        "<DIV STYLE='width:", 2*capWidth,
                 "px;height:", 2*capHeight,
                 "px;filter:progid:DXImageTransform.Microsoft.iris(irisStyle=circle,motion=out);"
                 //,"border:1px solid red;"
    );
    // move left/top so that only relevant corner shows
    if (edge.contains("R")) output.append("margin-left:", -capWidth, "px;");
    if (edge.contains("B")) output.append("margin-top:", -capHeight, "px;");

    // NOTE: needs overflow:hidden or it will expand to one line-height
    output.append("'><DIV STYLE='overflow:hidden;width:", capWidth, "px;height:", capHeight, 
                  "px;background-color:", this.cornerClipColor, ";");
                  //"px;background-color:orange;");

    // move right/bottom to place in relevant corner of 4x area container
    if (edge.contains("R")) output.append("margin-left:", capWidth, "px;");
    if (edge.contains("B")) output.append("margin-top:", capHeight, "px;");
    output.append("'></DIV></DIV>");

    //this.logWarn(this.getCallTrace() + ", html: " + output.toString());

    return output.toString();

    

},

	


//<CornerClips

//>RoundCorners




_edgesAsPeer : function () {
    return this.showEdges && !this.edgesAsChild;
},

_createEdges : function () {
    if (!this.showEdges || isc.isA.EdgedCanvas(this) || this._edgedCanvas != null) {
        return this._edgedCanvas;
    }
    var edgedCanvas = this._edgedCanvas = this._createEdgedCanvas();

    if (this.edgesAsChild) {
        edgedCanvas.resizeTo("100%", "100%");
        edgedCanvas.sendToBack();
        this.addChild(edgedCanvas);
    } else {
        this.addPeer(edgedCanvas);
    }
    return edgedCanvas;
},

//> @attr canvas.showEdges (boolean : false : IR) 
// Whether an +link{class:EdgedCanvas} should be used to show image-based edges around this
// component.
//
// @group imageEdges
// @visibility roundCorners
// @example edges
//<

//> @attr canvas.edgeSize (number : 10 : IR)
// @include edgedCanvas.edgeSize
// @example edges
//<

//> @attr canvas.edgeOffset (number : null : IR)
// @include edgedCanvas.edgeOffset
// @example corners
//<

//> @attr canvas.edgeImage (SCImgURL : "[SKIN]edge.gif" : IR)
// @include edgedCanvas.edgeImage
// @example edges
//<

//> @attr canvas.customEdges (Array of String : null : IR)
// @include edgedCanvas.customEdges
//<

//> @attr canvas.edgeBackgroundColor (Color : null : IR)
// Background color for the EdgedCanvas created to decorate this component.  This can be used
// to provide an underlying "tint" color for translucent edge media
// 
// @group imageEdges
// @visibility roundCorners
//<

//> @attr canvas.edgeOpacity (int : null : IR)
// Opacity of the edges.  Defaults to matching this.opacity.
// if +link{Canvas.setOpacity()} is called on a Canvas where edgeOpacity is set,
// edgeOpacity will be considered a percentage of the parent's opacity (so 50% opaque parent plus
// edgeOpacity 50 means 25% opaque edges)
// @setter setEdgeOpacity()
// @visibility external
// @example edges
//<

//> @attr canvas.edgeShowCenter (boolean : false : IR)
// @include edgedCanvas.showCenter
// @example corners
//<

//> @attr canvas.edgeCenterBackgroundColor (Color : null : IR)
// @include edgedCanvas.centerBackgroundColor
//<

//>	@method	canvas.setEdgeOpacity()
// Set the +link{edgeOpacity} and mark the canvas for redraw
// @param	newOpacity	(number)	new edge-opacity level
// @visibility external
// @example edges
//<
setEdgeOpacity : function (newOpacity) {
    var realOpacity = this.edgeOpacity = newOpacity;
    if (this.opacity > 0 && this.opacity < 100) {
        realOpacity = this.opacity * (this.edgeOpacity / 100);
    }
    this._edgedCanvas.setOpacity(realOpacity);
},


_edgePassThroughs : [
    "edgeImage", "edgeColor", "customEdges", "shownEdges",
    "edgeSize", "edgeTop", "edgeBottom", "edgeLeft", "edgeRight",
    "edgeOffset", "edgeOffsetTop", "edgeOffsetBottom", "edgeOffsetLeft", "edgeOffsetRight",
    "canDragResize", "canDragReposition"
],
_createEdgedCanvas : function () {
    // pass through edge-related properties
    var propNames = this._edgePassThroughs,
        edgedCanvas = isc.EdgedCanvas.createRaw();
    edgedCanvas.autoDraw = false;
    edgedCanvas._generated = true;
    edgedCanvas.dragTarget = this;
    
    edgedCanvas.visibility = this.visibility;
    edgedCanvas.opacity = this.opacity;
    if (this.edgeOpacity != null) {
        edgedCanvas.opacity = this.edgeOpacity;
        edgedCanvas._setOpacityWithMaster = false;
    }
    edgedCanvas.smoothFade = this.smoothFade;
    
    
    if (this.edgeOverflow != null) edgedCanvas.overflow = this.edgeOverflow;
    
    // edged canvases behavior like super-borders, so have the thing they're 
    // attached to handle events occurring on the edge (as it would with standard CSS borders)
    edgedCanvas.eventProxy = this;
    
    for (var i = 0; i < propNames.length; i++) {
        var name = propNames[i];
        if (this[name] != null) edgedCanvas[name] = this[name];
    }
    if (this.edgeBackgroundColor) edgedCanvas.backgroundColor = this.edgeBackgroundColor;    
    if (this.edgeCenterBackgroundColor) {
        edgedCanvas.centerBackgroundColor = this.edgeCenterBackgroundColor;    
    }
    if (this.edgeShowCenter != null) edgedCanvas.showCenter = this.edgeShowCenter;
    if (!this.edgesAsChild) edgedCanvas.zIndex = this.getZIndex(true)-1;

    edgedCanvas.completeCreation();
    return edgedCanvas;
},


// NOTEs on shadow placement and softness
// "thrower" means the element that throws the shadow.
//
// Possible properties for configuring shadows:
//
// - offset
//   How far the shadow is offset from the thrower
//   Physically:
//   - with a fixed light source, offset increases with thrower height
//   - a more angular light source causes larger offsets for all throwers, and a more distant
//     light source causes less offset
//   - technically, viewer angle would also change offset, but we assume the viewer is centered
//
// - softness
//   How much blurring there is along the shadows edges, and how much larger the shadow is than
//   the element that throws it.
//   Physically:
//   - with a fixed light source, softness increases with thrower height
//   - a light source closer to the page causes larger, softer shadows for all throwers
//
// - angle 
//   Direction shadow is offset.  This is almost always 45 degrees down/right, and we don't
//   support altering this
//
// - depth
//   A combination of softness and offset.
//   Physically, given a single light source and thrower height, both softness and
//   offset are known.  For a given depth, we implement an arbitrary default relationship
//   between depth and softness (implying a particular light source distance) and between depth
//   and offset (implying a particular light source distance and angle)
// 
// Sample softness/offset relations known to look nice:
//
// softness    offset    shadow pixels visible left/above   shadow pixels visible right/below
// 1           1         0                                  2
// 2           1         1                                  3
// 3           2         1                                  5
// 6           2         4                                  8

//> @attr canvas.showShadow     (boolean : false : [IR])
// Whether to show a drop shadow for this Canvas
//
// @visibility external
// @group shadow
// @example shadows
//<

//> @attr canvas.shadowDepth    (number : 4 : [IR])
// Depth of the shadow, or the virtual height above the page of the widget throwing the shadow.
// <P>
// This is a single parameter that can be used to control both <code>shadowSoftness</code> and
// <code>shadowOffset</code>.
//
// @visibility external
// @group shadow
//<
shadowDepth: 4,

//> @attr canvas.shadowOffset   (number : null : [IRA])
// Offset of the shadow.  Defaults to half of <code>shadowDepth</code> if unset.
// <P>
// Because of the blurred edges, a shadow is larger than the originating component by
// 2xsoftness.  An <code>shadowOffset</code> of 0 means that the shadow will extend around the
// originating component equally in all directions.
//
// @visibility external
// @group shadow
// @example shadows
//<

//> @attr canvas.shadowSoftness (number : null : [IRA])
// Softness, or degree of blurring, of the shadow.
// <P>
// A shadow with <code>softness:x</code> is 2x pixels larger in each direction than the element
// throwing the shadow, and the media for each edge should be x pixels wide/tall.
// <P>
// Defaults to <code>shadowDepth</code> if unset.
//
// @visibility external
// @group shadow
// @example shadows
//<

//> @attr canvas.shadowImage   (SCImgURL : "[SKIN]ds.png" : [IRA])
// Base name of the series of images for the sides, corners, and center of the shadow.
// <P>
// The actual image names fetched for the dropShadow combine the segment name and the
// <code>shadowDepth</code> setting.  For example, given "ds.png" as the base name, a depth of
// 4, and the top-left segment of the shadow, we'd use "ds4_TL.png".
// <P>
// The names for segments are the same as those given for controlling resizable edges; see
// +link{attr:canvas.resizeFrom}.  The center segment has the name "center".  The center segment is
// the only segment that doesn't include the depth in the URL, so the final image name for the
// center given a baseName of "ds.png" would be just "ds_center.png".
//
// @visibility external
// @group shadow
//<

//> @method canvas.setShowShadow()
// Method to update +link{canvas.showShadow}.
// @param showShadow (boolean) true if the shadow should be visible false if not
// @visibility external
// @group shadow
//<
setShowShadow : function (showShadow) {
    this.showShadow = showShadow;
    if (showShadow) {
        if (!this._shadow) this._createShadow();
        else if (this.isDrawn()) this._shadow.show();
    } else {
        if (this._shadow) this._shadow.hide();
    }
},

_createShadow : function () {
    var shadow = this._shadow = this.createAutoChild("shadow", 
                                                     {visibility:this.visibility,
                                                      zIndex:this.getZIndex(true)-3}, 
                                                     isc.DropShadow);
    this.updateShadow(true);

    this.addPeer(shadow);
    shadow.moveBelow(this);
},

// whether to allow drag resizing from the shadow.  Generally useful as the shadow, if present,
// occludes other elements and is hence a dead zone in terms of interactivity without this
// behavior.

dragResizeFromShadow:true,

updateShadow : function (initTime) {
    if (!initTime) this.setShowShadow(this.showShadow);
    var shadow = this._shadow;
    if (!shadow) return;

    shadow.offset = this.shadowOffset;
    shadow.offsetX = this.shadowOffsetX;
    shadow.offsetY = this.shadowOffsetY;
    shadow.softness = this.shadowSoftness;
    
    if (this.shadowImage) shadow.setEdgeImage(this.shadowImage);

    // NOTE: setDepth recalculates offsets and softness, even if depth change is a no-op
    shadow.setDepth(this.shadowDepth);

    if (this.dragResizeFromShadow && this.canDragResize) {
        // NOTE: master's setting for canDragResize is dynamically checked via overrides on the
        // DropShadow class
        shadow.canDragResize = this.canDragResize;
        shadow.resizeFrom = this.resizeFrom;
        shadow.dragTarget = this;
    }
},
//<RoundCorners

_$shadow:"shadow",
propertyChanged : function (propName, value) {
    if (isc.contains(propName, this._$shadow) && this.updateShadow) this.updateShadow();
},

// Group Frame APIs
// ---------------------------------------------------------------------------------------

// isGroup - should we show a grouping frame around this canvas

isGroup:false,

setIsGroup : function (isGroup) {
    if (isGroup == this.isGroup) return;
    // horrible hack: we use registerAttachedPeer to account for the space taken up
    // by the label with margins - for whatever reason this doesn't update on a drawn widget
    // without a clear/draw(), so explicitly clear / draw if necessary
    var mustClear = this.shouldShowGroupLabel() && this.isDrawn();
    if (mustClear) this.clear();
    if (isGroup) {
        this._standardBorder = this.border;
        this.setBorder(this.groupBorderCSS);
        if (this.shouldShowGroupLabel()) this._showGroupLabel();
    } else {
        this.setBorder(this._standardBorder || "");
        if (this.shouldShowGroupLabel()) this._hideGroupLabel();
    }
    this.isGroup = isGroup;
    if (mustClear) this.draw();
},
groupBorderCSS:"2px solid black",
groupLabelPadding:10,

showGroupLabel:true,
shouldShowGroupLabel : function () {
    return this.showGroupLabel;
},

// creates the groupLabel canvas (and sets default properties)
makeGroupLabel : function () {
    
    if (!this.groupLabel) {
        var dynamicDefaults = {
            autoDraw:false, _resizeWithMaster:false, _moveWithMaster:true,
            backgroundColor:this.getGroupLabelBackgroundColor(),
            eventProxy:this
        }
        if (this.groupTitle != null) dynamicDefaults.contents = this.groupTitle;        
        this.groupLabel = this.createAutoChild("groupLabel", dynamicDefaults);

    } else {
        if (this.groupTitle != null) this.groupLabel.setContents(this.groupTitle);
        this.groupLabel.setBackgroundColor(this.getGroupLabelBackgroundColor());
    }
},

// default groupLabel background color to match us, or be white if we're transparent
getGroupLabelBackgroundColor : function () {
    if (this.groupLabelBackgroundColor) return this.groupLabelBackgroundColor;
    if (this.backgroundColor != null) return this.backgroundColor;
    // should check this.styleName.backgroundColor too...
    return "white";
},

_showGroupLabel : function () {

    this.makeGroupLabel();
    
    var label = this.groupLabel;
    // draw the groupLabel offscreen so we can pick up vertical sizing
    var labelHeight;
    if (label.overflow == isc.Canvas.VISIBLE) {
        // Ensure it's top level before trying to draw it offscreen
        if (label.parentElement != null) label.deparent();
        label.setTop(-1000);
        label.draw();
        labelHeight = label.getVisibleHeight();

    } else {
        labelHeight = label.getVisibleHeight();
    }
    // the groupLabel will be an attached peer - this handles updating the top margin
    var offset = Math.round(labelHeight / 2);
    this._registerAttachedPeer(label, isc.Canvas.TOP, offset);
    
    // very crude- explicitly set topPadding to ensure there's enough space
    
    var padding = labelHeight - offset;
    if (this.padding) padding += this.padding;
    this.setTopPadding(padding);
    
    // can update this to support center / right alignment fairly easily
    label.setLeft(this.getLeft() + this.groupLabelPadding);
    label.setTop(this.getTop());
    if (label.masterElement != this) this.addPeer(label);
    if (this.isDrawn()) {
        if (!label.isDrawn()) label.draw();
    }
    // xxx hack: run calculate margins to ensure the visible height of the label is remembered
    // as top margin (it will likely be cleared before being added to layouts etc)
    this.getTopMargin(); 
    label.moveAbove(this);
    // if we had to draw the label offscreen, and we're not yet drawn, clear now.
    if (label.isDrawn() && !this.isDrawn()) label.clear();
},
_hideGroupLabel : function () {
    if (!this.groupLabel) return;
    var label = this.groupLabel;
    this._unRegisterAttachedPeer(label, isc.Canvas.TOP);
    this.setTopPadding(null);
    label.clear();
    // xxx this depeer is required to ensure it doesn't draw with us next time we get drawn!
    label.depeer();
}, 

//groupLabelConstructor:"Label"
groupLabelDefaults:{
    // default to a Label
    _constructor:"Label",
    // which fits its content
    overflow:"visible", 
    height:1, width:1,
    padding:5, wrap:false,
    // center in both directions
    vAlign:"center", align:"center"
},

setGroupTitle : function (newTitle) {
    this.groupTitle = newTitle;
    if (this.groupLabel) {
        this.groupLabel.setContents(this.groupTitle);
    } else {
        this._showGroupLabel();
    }
}

});



isc.Canvas.addClassMethods({



// for canvas start/end in useCaptureSpan mode.
// 
// Note: we need to strip ALL script tags - i.e. not just the Javascript ones so that
// e.g. VBScript doesn't re-execute. That's why we don't reuse code from HTMLFlow here.
//
// Note: these will incorrectly strip matching text in e.g. strings and textareas - but that
// should be a somewhat unlikely occurence.
//
// Furthermore, we can provide these as semi-public override points for easy patching if anyone
// runs into a problem.
stripScriptTags : function (html) {
    // \r required for Firefox 1.0 (and probably earlier moz) - otherwise doesn't match EOL
    return html.replace(/<script([^>]*)?>(.|\n|\r)*?<\/script>/ig, isc.emptyString);
},
stripLinkTags : function (html) {
    return html.replace(/<link([^>]*)?>/ig, isc.emptyString);
},


// DOM emulation
// --------------------------------------------------------------------------------------------

//> @classMethod Canvas.getById()
// Retrieve a Canvas by it's global +link{canvas.ID,ID}.
// @param ID (String) global ID of the Canvas
// @return (Canvas) the Canvas, or null if not found
// @visibility external
//<
getById : function (sId) {
    var canvas = window[sId] || null;
    return canvas ? (isc.isA.Canvas(canvas) ? canvas : null) : null;
},

// get the next zIndex for the next item to be drawn. see setZIndex() for notes
getNextZIndex : function () {
    return (isc.Canvas._nextZIndex += 18);
},

// getFocusProxyString()
// This will return HTML for a natively-focusable element with no visual representation to be 
// written into the DOM.

getFocusProxyString : function (tagBaseName, absolute, offsetLeft, offsetTop, width, height, isVisible, 
                                canFocus, tabIndex, accessKey, eventsHandledNatively,
                                focusHandler, blurHandler,
                                keyDownHandler, keyPressHandler, keyUpHandler) 
{
    if (this._focusProxyTemplate == null) {
        this._onfocus = "' ONFOCUS=";
        this._closeQuoteSpace = "' ";
        this._onblur = " ONBLUR=";
        this._focusProxyTemplate = [
            "<div",                                                     // 0
            " id='",                                                     // 1
                null,                                                   // 2 - tagBaseName
            "__focusProxyParent'" +                                      
            " style='overflow:hidden;width:0px;height:0px;position:",   // 3
                ,                                                       // 4 - position (absolute/inline)
                ";left:",                                               // 5
                    null,                                               // 6 - offsetLeft
                "px;top:",                                              // 7
                    null,                                               // 8 - offsetTop
                "px;'>",                                                // 9

            
            (isc.Browser.isSafari ? 
                "<textarea" :
                (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111 ? 
                    "<div" :
                            
                    "<button onclick='event.cancelBubble=true;return false;'"
                )
            ),                                                          // 10
            
                " id='",                                                 // 11
                    null,                                               // 12 - tagBaseName
                "__focusProxy'",                                         // 13

                // Note: if we are not visible, draw the focusProxy as not being visible either 
                // - this prevents it from being focus-able, until we get shown
                " style='VISIBILITY:",                                  // 14
                    null,                                               // 15 - visible / hidden

                // the proxy button is drawn inside it's 0x0 parent div (with overflow hidden)
                // Note - if you make parentDiv bigger than 0x0, and position the focus proxy 
                // such that it's initially not visible, when it receives focus, the parent 
                // div scrolls, making it visible (at least in Moz)
                // The 0x0 div is supported in both Moz and Safari, so use this to ensure it's
                // not visible
                "left:1px;top:1px;" +                  

                
                "width:",                                               // 16
                    (isc.Browser.isSafari ? "1" : null),                // 17 - width
                "px;height:",                                           // 18
                    (isc.Browser.isSafari ? "1" : null),                // 19 - height
                "px;",                                                  // 20
                    null,                                               // 21 - -moz-user-focus (Moz Only)
                this._onfocus,                                           // 22
                    null,                                               // 23 - focusHandler
                this._onblur,                                             // 24
                    null,                                               // 25 - blurHandler
                null, null, null,                                       // 26-28 - onKeyDown/up/press
                " tabindex='",                                           // 29
                    null,                                               // 30 - TabIndex
                    null,                                               // 31 - "ACCESSKEY=x"
                                
                // Hang this attribute on the tag so we can recognize it's not a native form 
                // item in EH.eventHandledNatively      
                "' focusProxy='true' handleNativeEvents='",                 // 32
                    null,                                               // 33 - true / false
                "'>",                                    
//                tagBaseName," focus proxy" +
                (isc.Browser.isSafari ? "</textarea>" : 
                    (isc.Browser.isMoz && isc.Browser.geckoVersion >= 20051111 ? "</div>" 
                                                                               : "</button>")
                ),   
                "</div>"                                                
        ]
    }

    var template = this._focusProxyTemplate;
    
    template[2] = tagBaseName;
    template[4] = (absolute ? "absolute" : "inline");
    template[6] = offsetLeft;
    template[8] = offsetTop;
    template[12] = tagBaseName;
    template[15] = (isVisible ? "visible;" : "hidden;");
    template[17] = width;
    template[19] = height;
    if (isc.Browser.isMoz) {
        if (!canFocus || tabIndex == -1) template[21] = "-moz-user-focus:ignore;";
        else template[21] = "-moz-user-focus:normal;"
    }
    if (focusHandler && focusHandler != isc.emptyString) {
        template[22] = this._onfocus;
        template[23] = focusHandler;
    } else {
        template[22] = this._closeQuoteSpace; // close the style=' attribute
        template[23]= null
    }
    if (blurHandler && blurHandler != isc.emptyString) {
        template[24] = this._onblur;
        template[25] = blurHandler
    } else {
        template[24] = null;
        template[25] = null;
    }

    // Only write key handlers in if they were passed in
    
    template[26] = (keyDownHandler != null ? " onkeydown=" + keyDownHandler : null);
    template[27] = (keyPressHandler != null ? " onkeypress=" + keyPressHandler : null);
    template[28] = (keyUpHandler != null ? " onkeyup=" + keyUpHandler : null);

    template[30] = (canFocus ? tabIndex : -1);
    template[31] = (canFocus && accessKey ? "' accesskey='" + accessKey : null);
    
    template[33] = (eventsHandledNatively ? true : false);
    
    return template.join(isc._emptyString);
                          
},


// CSS Caching
// ---------------------------------------------------------------------------------------

// wipe out any cached CSS information.  This is needed for 
// - Safari 2.0 and earlier where we get bad info before page load
// - automated tests that load stylesheets
// - possible future advanced usage like on the fly skin change
clearCSSCaches : function () {

    // tell the Element class to clear all generic CSS caches
    isc.Element._clearCSSCaches();

    // wipe out cached style information on Canvases
    var list = isc.Canvas._canvasList;
    for (var i = 0; i < list.length; i++) {
        var canvas = list[i];
        if (canvas == null || canvas.destroyed) continue;
        canvas._fullMargins = canvas._cachedMargins = 
            canvas._cachedBorderSize = canvas._cachedPadding = null;
    }
},


// Image locations and skinning
// --------------------------------------------------------------------------------------------

//>!BackCompat 2005.2.23 
// Removing these setters and getters, as switching images to a different directory on the
// fly is a pointless feature, and getters don't seem necessary. 

//>	@classMethod Canvas.setAppImgDir()
// Set the default app-specific image directory for all canvases of this type.
// <p>
// Note: this will not cause any instances to redraw, but having them
// redraw will show the new images.
//
// @param URL		(string)	New URL for the app-specific images.
// @group images
//<
setAppImgDir : function (URL) {
	this.getPrototype().appImgDir = URL;
},

//>	@classMethod Canvas.getAppImgDir()
// Return the image directory for this class of widgets, prepended with the Page image
// directory.
//
// @group images
// @return (URL)	Image directory (including Page image directory) for this widget.
//<
getAppImgDir : function () {
	return isc.Page.getImgURL(isc.emptyString, this.getPrototype().appImgDir);
},


//>	@classMethod Canvas.setSkinImgDir()
// Set the default widget image directory for all canvases of this type.
// <p>
// Note: this will not cause any instances to redraw, but having them
// redraw will show the new images.
//
// @param URL		(string)	New URL for the app-specific images.
// @group images
//<
setSkinImgDir : function (URL) {
	this.getPrototype().skinImgDir = URL;	
},

//>	@classMethod	Canvas.getSkinImgDir()
// Return the image directory for this class of widgets, prepended with the Page image
// directory.
//
// @group images
// @return (URL)	Image directory (including Page image directory) for this widget.
//<
getSkinImgDir : function () {
	return isc.Page.getSkinImgDir(this.getPrototype().skinImgDir);
},

//<!BackCompat

// --------------------------------------------------------------------------------------------

// see JSDoc for instance method canvas.getImgURL()
_skinPrefix : "[SKIN]",
getImgURL : function (src, imgDir, instance) {
	// if no src specified, return empty string
	if (src == null || isc.isAn.emptyString(src)) return isc._emptyString;

    // get skin / app dir settings from the passed-in instance or use this class' instance
    // prototype to get instance defaults.
    instance = instance || this.getPrototype();
	
	// handle src being specified as an object, of the form:  {src:"URL", imgDir:"URL"}
    if (src.imgDir != null && imgDir == null) imgDir = src.imgDir;
    if (src.src != null) src = src.src;

	// default the imgDir as appropriate
	if (imgDir == null) {
		imgDir = (isc.startsWith(src, this._skinPrefix) ? instance.skinImgDir : instance.appImgDir);
	}
	var URL = isc.Page.getImgURL(src, imgDir);

    //>DEBUG
    //this.logDebug("getImgURL("+src+","+imgDir+") returned " + URL);
    //<DEBUG
	return URL;
},

// Printing
// --------------------------------------------------------

// for printHTML
printOmitControls : [
"Button","StretchImgButton","ImgButton","MenuButton",
"Toolbar","ToolStrip","ButtonItem","ToolbarItem"
],
printIncludeControls : [
"Label"
],

//> @classMethod Canvas.getPrintHTML()
// Returns print-formatted HTML for a number of components in the page.
// @param components (array of Canvas) Components to get the print HTML for. Strings of raw HTML may
//  also be included in this array, and will be integrated into the final HTML at the appropriate
//  point.
// @param [printProperties] (object) printProperties object to pass to getPrintHTML() method for
//  the various components.
// @param callback (callback) Callback to fire when the method completes. The generated print HTML
//  will be passed in as the first paramter <code>HTML</code>.
// @param separator (HTML) Optional HTML separator to render between each component's printable HTML 
// 
// @visibility printing
//<
// callback is also passed the callback as a second parameter to allow the developer to pass
// state around.
// HTML / index params are used internally - this method calls itself to handle asynchronous HTML
// generation. 
getPrintHTML : function (components, printProperties, callback, separator, HTML, index) {
    
    if (!isc.isAn.Array(components)) components = [components];
    
    if (HTML == null) HTML = [];
    if (index == null) index = 0;
    
    var async,
        componentCallback = {target:this, methodName:"gotComponentPrintHTML",
                             components:components, printProperties:printProperties,
                             callback:callback, HTML:HTML, index:index, separator:separator};
                               
    for (; index < components.length; index++) {
        // if we fire the component level callback - start on the component after it in the list!
        componentCallback.index+=1;
        var component = components[index];
        
        // allow raw HTML strings 
        var compHTML;
        if (isc.isA.String(component)) compHTML = component;
        else compHTML = component.getPrintHTML(printProperties, componentCallback);
        
        if (compHTML != null) {
            HTML.add(compHTML);
        } else {         
            async = true;
            break;
        }        
    }

    // if we went asynchronous, we'll run again
    if (async) {
        if (!callback) { 
            this.logWarn("getPrintHTML(): HTML generated asynchronously, but no callback passed in");
        }
        return null;
    }
    if (callback) {
        this.fireCallback(callback, "HTML,callback", [HTML.join(separator || isc.emptyString),
                                                      callback]);
    }

    return HTML.join(separator || isc.emptyString);
},
        

gotComponentPrintHTML : function (HTML, callback) {
    callback.HTML.add(HTML);
    this.getPrintHTML(callback.components, callback.printProperties, callback.callback, 
                      callback.separator, callback.HTML, callback.index);
},


// HTML for Images (and other basic structures)
// --------------------------------------------------------------------------------------------

//>	@classMethod Canvas.imgHTML()
//			Return the HTML for an image.
//
//		@group	images
//		@param	src				(SCImgURL)
//		NOTE: instead of passing several parameters, you can pass an object as the 'src'
//      parameter with properties for all the various function parameters with, eg:<br>
//      canvas.imgHTML( {src:"foo", width:10, height:10} );
//		@param	[width]			(number)
//		@param	[height]		(number)
//		@param	[name]			(string)
//		@param	[extraStuff]	(string)
//		@param	[imgDir]		(string)
//
//		@return	(string)	configured IMG tag
//<

getImgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML, 
                       instance, returnTemplate) {
	return this.imgHTML(src, width, height, name, extraStuff, imgDir, activeAreaHTML, 
                        instance, returnTemplate);
},

_getImgHTMLTemplate : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML) {
    return isc.Canvas.imgHTML(src, width, height, name, 
                              extraStuff, imgDir, activeAreaHTML, null, true);
},

// - if "instance" is passed, we into account the instance settings for imgDir and make the IMG
//   ID unique to the instance.
// - if "returnTemplate" is passed, we return an HTML template Array, with a slot open to give
//   a unique ID to the image.  This is a very advanced internal API for generating many images
//   with the same SRC, size and other attributes but with different unique IDs.
_imgMapId : 0,
imgHTML : function (src, width, height, name, extraStuff, imgDir, activeAreaHTML, 
                    instance, returnTemplate) {

    var align;
	// if an object is passed in for SRC, assume that it's a properties object
	//	and normalize it into the arguments of the function
	if (isc.isAn.Object(src)) {
		if (src.width != null) 			width = src.width;
		if (src.height != null) 		height = src.height;	
		if (src.name != null) 			name = src.name;
		if (src.extraStuff != null) 	extraStuff = src.extraStuff;	
		if (src.imgDir != null) 		imgDir = src.imgDir;
        if (src.align != null)          align = src.align;
        if (src.activeAreaHTML != null)    activeAreaHTML = src.activeAreaHTML;        
		src = src.src;
	}    
    
    if (src == null || isc.isAn.emptyString(src)) { 
        return (returnTemplate ? [isc._emptyString] : isc._emptyString);
    }
 
    // once ever setup
    var template = this._imgTemplate;
    if (!template) {
        this._imgSrc = "<img src='";
        this._widthEquals = "' width='";
        this._heightEquals = "' height='";
        this._alignEquals = "' align='";
        this._nameEquals = (isc.Page.isXHTML() ? "' id='" : "' name='");
        this._closeQuote = "' ";
        // NOTE: Opera converts TEXTTOP to "bottom" when retrieved from the DOM, and align is
        // way off.  "middle" seems close to what "TEXTTOP" used to mean
        this._textTop = isc.Browser.isOpera ? "middle" : "TEXTTOP";	
        this._endString = " border='0' suppress='TRUE'/>";
        this._imgTemplate = template = [this._imgSrc];

        if (isc.Browser.isIE8Strict) {
            
            this._alphaFilterStart = 
                "' style='filter:\"progid:DXImageTransform.Microsoft.AlphaImageLoader(src="; 
            this._alphaFilterEnd = ",sizingMethod=scale)\";";
        } else {
            this._alphaFilterStart = 
                "' style='filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\""; 
            this._alphaFilterEnd = "\",sizingMethod=\"scale\");";
        }
    }
    // default align to texttop (this._textTop defined above)
    if (align == null) align = this._textTop;
    
    
    if (!this._blankURL) this._blankURL = this.getImgURL("[SKIN]/blank.gif");

    //>DEBUG
    if (isc.Browser.isSafari && (width > 32000 || height > 32000)) {
        this.logWarn("Attempting to draw an image of size " + width + " x " + height + 
                    ".  Images larger than 32000 pixels in either direction are not reliably " +
                    " rendered in this browser.");
    } //<DEBUG
    
	var URL = this.getImgURL(src, imgDir, instance);

    // if we're being asked to return a template, allocate a fresh one that the caller can hang
    // onto
    if (returnTemplate) template = [this._imgSrc];

    // fill out the template.  NOTE: if the numbering changes here, all callers to
    // _getImgHTMLTemplate() need to be updated.
    // [0] "<img src='"
    // [1] URL
    // [2] [unused]
    // [3-5] IE filtering
    // [6] "' width="
    // [7] width
    // [8] "' height="
    // [9] height
    // [10] "' align="
    // [11] align
    // [12] "' name="
    // [13] canvas ID
    // [14] name
    // [15] "' usemap=" + mapName
    // [16] "' "
    // [17] extraStuff
    // [18] endString
    // [19] (optional) "<map name='.....></map>"
    // in XHTML mode 
    if (isc.Page.isXHTML()) URL = isc.makeXMLSafe(URL);

    if (!this._fixPNG(instance) || !this._isPNG(src)) {
        template[1] = URL;
	} else {
	    
		template[1] = this._blankURL;
		template[3] = this._alphaFilterStart;
        template[4] = URL;
        template[5] = this._alphaFilterEnd;  

        
        if (width == null) width = 16;
        if (height == null) height = 16;
    }

    if (width) {
        template[6] = this._widthEquals;
        template[7] = width;
    }
    if (height) {
        template[8] = this._heightEquals;
        template[9] = height;
    }

    template[10] = this._alignEquals;
    template[11] = align;

    if (name) {
        template[12] = this._nameEquals;
        // make the name unique to the target instance if passed one
        if (instance) template[13] = instance.getCanvasName();
        template[14] = name;
    }
    // img map support
    var mapName;
    if (activeAreaHTML) {
        mapName = "ISC_IMGMAP_" + this._imgMapId++;
        template[15] = "' usemap=#" + mapName;
    }
    template[16] = this._closeQuote;

    if (extraStuff) {
        template[17] = extraStuff;
    }
    template[18] = this._endString;
    if (activeAreaHTML) {
        template[19] = "<map name='" + mapName + "'>" + activeAreaHTML + "</map>";
    }
    
    if (returnTemplate) return template;

    // otherwise return the HTML and truncate the template
    var output = template.join(isc._emptyString);
    template.length = 3;
    return output;
},



// Value Icon HTML generation
// Generates the <img ...> tag HTML used by ListGrids and DynamicForm items for their 'valueIcons'

_$IDEquals:"ID='",
_$singleQuote:"'",
_$absmiddle:"absmiddle",
_$valueIconExtraStuffTemplate : [
    ,                                                   // [0] ID=', or null
    ,                                                   // [1] ID, or null
    ,                                                   // [2] ', or null
    
    " eventpart='valueicon' style='vertical-align:middle;margin-left:",       // [3]
    ,                                                   // [4] padding on left of icon
    "px;margin-right:",                                 // [5]
    ,                                                   // [6] padding on right of icon
    "px;'"                                              // [7]
],
_valueIconObj : {},
_getValueIconHTML : function (src, prefix, width, height, leftPad, rightPad, ID, instance) {

    // Apply ID and custom styling to the image through the 'extraStuff' parameter    
    var extraStuffTemplate = this._$valueIconExtraStuffTemplate;
    if (ID != null) {
        extraStuffTemplate[0] = this._$IDEquals;
        extraStuffTemplate[1] = ID;
        extraStuffTemplate[2] = this._$singleQuote;
    } else {  
        extraStuffTemplate[0] = extraStuffTemplate[1] = extraStuffTemplate[2] = null;
    }
    
    extraStuffTemplate[4] = leftPad || 0;
    extraStuffTemplate[6] = rightPad || 0;
    
    var src = isc.Canvas.getImgURL(src, prefix, instance),
        extraStuff = extraStuffTemplate.join(isc.emptyString),
        iconObj = this._valueIconObj;
        
    iconObj.src = src;
    iconObj.width = width
    iconObj.height = height
    // We want the valueIcon to be center-aligned with adjacent text
    // (either the form item's textBox text, or a listGrid cell's text)
    // We do this by setting align='absMiddle', and vertical-align = middle
    
    if (height != null && height < 16 && (isc.Browser.isMoz || isc.Browser.isSafari)) {
        iconObj.align = null;
    } else {
        iconObj.align = this._$absmiddle; // prevent default "text-top"
    }
    iconObj.imgDir = prefix;
    iconObj.extraStuff = extraStuff;
    
    return isc.Canvas.imgHTML(iconObj);
},

// NOTE: Whether to apply IE5.5+ PNG alpha transparency workaround.
// IE7 natively supports PNG transparency, however if you also set opacity via the
// Microsoft.Alpha filter, PNG transparency breaks. 
// This is visible with fade animations, and with transparent hovers with dropShadows (since
// the shadows, which are peers, get the master's transparency), 
// This is obliquely mentioned in the blog where PNG transparency support was first announced:
// - http://blogs.msdn.com/ie/archive/2005/04/26/412263.aspx
// Getting rid of filters greatly reduces browserDoneDrawing() time, so it might be a
// worthwhile optimization to special case certain PNG-heavy widgets, like so:
//  - in _fixPNG(), allow an instance flag that avoids using filter hacks for PNG transparency so
//    long as opacity is not set
//  - set this flag for DropShadow only
//  - in setOpacity() override on DropShadow, redraw() to cause filters to be used
//  - NOTE: in order to generalize this to all Canvii or even all EdgedCanvas, would need
//    parent->child opacityChange notifications since setOpacity can be called on a parent.
//
// - Update: 6/15/2007 IE 7.0.5730.11
// IE7 has blurriness at PNG edge on a PNG *without* filters if a filter is used elsewhere on
// the page.  An example is here:
//   http://www.atalasoft.com/cs/blogs/davidcilley/archive/2007/03/14/ie7-dximagetransform-and-png-transparency-problem.aspx
// For single pixel tiled PNGs, which SmartClient uses extensively for the center segment of
// buttons and for the "rails" on rounded corners, this blur translates to what looks like
// a fade effect on the stretched image.  
// Note that this effect is avoided for a PNG that has no alpha channel at all, which is a
// distinct file format from a PNG with an alpha channel with 100% opacity.  In many cases the
// alpha channel could be removed, but not for, eg, tintable SectionHeaders.
// 
// This basically means it's impossible to avoid using the AlphaImageLoader workaround for PNGs
// unless we *both* do not use any IE filters within the framework *and* insist that all
// developers who use SmartClient also do not use IE filters
//
// Preliminary investigation on IE8 beta (March 14 2008) indicates that, like IE7 if a filter is
// applied to introduce translucency the PNG alpha channel support is broken 
_fixPNG : function (instance) {
    
	var fix = isc.Browser.isIE && isc.Browser.minorVersion >= 5.5 && 
//                (isc.Browser.version < 7 || this.opacity == null) &&
                isc.Browser.isWin &&
                this.neverUsePNGWorkaround != true;
    // if we have an instance with _fixPNG returning false, respect it
    if (fix && instance && instance._fixPNG && !instance._fixPNG()) {
        fix = false;
    }
    return fix;
},

_$pngSuffixes:{
png:true, PNG:true, Png:true
// Could include pNG, etc too - probably not necessary
},
_isPNG : function (src) {
	return (src && this._$pngSuffixes[src.substring(src.lastIndexOf(isc.dot) + 1)]);
},

_setImageURL : function (imageElement, src, imgDir, instance) {
	// derive URL
    var URL = this.getImgURL(src, imgDir, instance);

    // apply new URL to image
    if (!this._fixPNG(instance)) {
        imageElement.src = URL;
    } else {
        var imgSrc = imageElement.src,
            wasPNG = this._isPNG(imgSrc),
            willBePNG = this._isPNG(src);

        if (willBePNG) {
            imageElement.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\"" +
                    URL + "\",sizingMethod=\"scale\")";
            if (!wasPNG) imageElement.src = this._blankURL;
        } else { // won't be PNG
            if (wasPNG) imageElement.style.filter = "";
            imageElement.src = URL;
        }
    }
},

//> @classMethod Canvas.linkHTML()
// Returns the HTML for a standard link element.
// @param href (string) target url for the link.
// @param [text] (string) text to display in the link element - if null, use the href
// @param [target] (string) target window for the link - defaults to "_blank"
// @param [ID] (string) optional ID for the link element
// @param [tabIndex] (number) optional tabIndex for the link
// @param [accessKey] (string) optionl accessKey for the link
// @visibility internal
//<
// @param extraStuff - allows you to add freeform attributes into the tag)

_$linkHTMLTemplate:[
    "<a",       // 0
    ,           // 1: (ID ? " ID='" + ID + "'" : ""),
    " href='",  // 2
    ,           // 3: href
    "' target='", // 4
    ,           // 5: target
    "'",        // 6
    ,           // 7: tabIndex = tabIndex or null
    ,           // 8: accessKey = accessKey or null
    ,           // 9: extraStuff
    ">",        // 10
    ,           // 11 text of the link
    "</a>"
],
linkHTML : function (href, text, target, ID, tabIndex, accessKey, extraStuff) {
   
   href = href.replaceAll("'", "\\'");

   if (text == null) text = href;
   
   target = target ? target.replaceAll("'", "\\'") : "_blank";
   
   var template = this._$linkHTMLTemplate;
   
   if (ID != null) template[1] = " ID='" + ID + "'";
   else template[1] = null;
   
   template[3] = href;
   template[5] = target;
   
   if (tabIndex != null) template[7] = " tabIndex=" + tabIndex;
   else template[7] = null;
   
   if (accessKey != null) template[8] = " accessKey='" + accessKey + "'";
   else template[8] = null;
   
   if (extraStuff) template[9] = " " + extraStuff;
   
   template[11] = text;   
   return template.join(isc.emptyString);

},




//>	@classMethod	Canvas.blankImgHTML()
//			Return the HTML for a blank image, such as would be used for a spacer.
//		@group	utils
//
//		@param	[width]			(number)
//		@param	[height]		(number)
//
//		@return	(string)	configured IMG tag
//<
_blankImgURL : "[SKINIMG]/blank.gif",
_$zero:"0",
blankImgHTML : function (width,height) {
    var template = this._blankTemplate;
    if (!template) {
        
        
        template = this._blankTemplate = 
	        this._getImgHTMLTemplate(this._blankImgURL, 1, 1);
    }
    template[7] = width || this._$zero;
    template[9] = height || this._$zero;
    return template.join(isc._emptyString);
},

//>	@classMethod	Canvas.spacerHTML()
//		Return the HTML for a blank spacer at a particular width and height.
//		Does this without using images so it should be really fast.
//
//		@group	utils
//
//		@param	[width]			(number)
//		@param	[height]		(number)
//
//		@return	(string)	HTML for the spacer
//<
spacerHTML : function (width, height, contents) {
    // shortcut: if the size is 0x0, return an empty string
    if (width == 0 && height == 0) return isc._emptyString;
    
    
    if (isc.Browser.isMoz || 
        isc.Browser.isSafari ||    
        isc.Browser.isOpera ||    
        isc.Browser.isStrict ||
        (height < 3 && isc.Browser.isIE && (isc.Browser.minorVersion == 5.5 || isc.Browser.isMac))) 
    {
        
        var threshold;
        if (isc.Browser.isSafari) {
            threshold = 32000;
        } else if (isc.Browser.isFirefox && isc.Browser.geckoVersion >= 20090219) {
            threshold = 17895580;
        } else if (isc.Browser.isIE && isc.Browser.isStrict) {
            threshold = 16000;
        }
        
        if (threshold != null && (width > threshold || height > threshold)) {
            var output = isc.SB.create(),
                max = threshold,
                // note - numRows / cols will be one less than is required
                numRows = Math.floor(height / max),
                numCols = Math.floor(width / max);

            output.append("<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0 MARGIN=0>");
            for (var i = 0; i <= numRows; i++) {
                output.append("<TR>");
                for (var j = 0; j <= numCols; j++) {

                    output.append("<TD>");
                    // write cells down leading diagonal, or along first row / col if we've
                    // already writen out all the cells we need on the other axis
                    var writeSpacer = 
                        ((i == j) || (i > numCols && j == 0) || (j > numRows && i == 0));
                        
                    if (writeSpacer) {
                        var cellSpacerHeight = (i < numRows ? max : height - (i*max)),
                            cellSpacerWidth = (j < numCols ? max : width - (j*max));

                        output.append(this.blankImgHTML(cellSpacerWidth, cellSpacerHeight));
                    } 
                    output.append("</TD>");
                }
                output.append("</TR>");
            }
            output.append("</TABLE>");
            return output.toString();
        }
        return this.blankImgHTML(width,height);
    }
        
    // return HTML that the browser recognizes as taking up space.  
    var spacerHTML = this._spacerHTMLTemplate;
    if (spacerHTML == null) {
        spacerHTML = this._spacerHTMLTemplate = [
            "<SPAN STYLE='WIDTH:", 
            null, // width
            "px;HEIGHT:", 
            null, // height
            "px;overflow:hidden;'>",
            null, // contents
            "</SPAN>"
        ];
    }
    spacerHTML[1] = width;
    spacerHTML[3] = height;
    spacerHTML[5] = contents ? contents : isc.nbsp;
    return spacerHTML.join(isc._emptyString);
},

//>	@classMethod canvas.hiliteCharacter()	(A)
//			Given a string and a character, hilite the first occurrance of the character in the
//          string (if it occurs), preferring uppercase to lowercase.
//
//		@group	utils
//
//		@param	string      (string)    String to return with hilited character
//		@param	character   (character) Character to hilite
//		@param	[hilitePrefix] (string) Prefix to apply to hilighted character - defaults to
//                                      "&lt;span style='text-decoration:underline;'&gt;"
//      @param  [hiliteSuffix]  (string)    Suffix to apply to hiliteed character - defaults to
//                                          "&lt;/span&gt;"
//
//		@return	(string)	The string passed in, with the first occurrance of the hilite
//                          character enclosed by the 'hilitePrefix' and 'hiliteSuffix'
// @visibility external
//<
// This is used by form items, and stretchImgButtons to hilite their accessKey
hiliteCharacter : function (string, character, hilitePrefix, hiliteSuffix) {

    if (!isc.isA.String(string) || !isc.isA.String(character) || character.length != 1) 
        return string;
    
    // Bail if they're attempting to hilight a space character - it will look weird!
    
    if (character == " ") return string;

    // Default the hilite prefix and suffix if necesary (note - we don't support being passed
    // just one of these arguments)
    if (hilitePrefix == null || hiliteSuffix == null) {
        hilitePrefix = "<span style='text-decoration:underline;'>";
        hiliteSuffix = "</span>"
    }
    
    var index = string.indexOf(character.toUpperCase());
    if (index == -1) index = string.indexOf(character.toLowerCase());

    if (index != -1) {
        var start = string.slice(0, index),
            hiliteString = string.slice(index, index+1),
            end = string.slice(index+1);
            
        hiliteString = hilitePrefix+hiliteString+hiliteSuffix;
        string = start.concat(hiliteString, end);
    }
                
    return string;    
},

// Redraw Queue
// --------------------------------------------------------------------------------------------

//>	@classMethod	Canvas.scheduleRedraw()	(A)
// 			Add a canvas that needs to be redrawn to the redrawQueue so it will be redrawn
//          automatically.  Called by Canvas.markForRedraw()
//		@group	draw
//
//		@param	canvas		(Canvas)		Canvas to be redrawn
//<
_$clearRedrawQueue:"clearRedrawQueue",
scheduleRedraw : function (canvas) {
    //this.logWarn("Scheduled redraw of: " + canvas + this.getStackTrace());

	// add the canvas to the list of canvases to be redrawn
    if (canvas && canvas.priorityRedraw) {
        this._redrawQueue.addAt(canvas, 0);
    } else {
        this._redrawQueue.add(canvas);
    }
	// and start the timer to redraw the objects in the queue
	if (!this._redrawTimer) {
		this._redrawTimer = 
            isc.Timer.setTimeout({target:isc.Canvas, methodName:this._$clearRedrawQueue}, this._redrawQueueDelay);
	}
},

//>	@classMethod	Canvas.clearRedrawQueue()	(A)
// 		Redraw all the canvases that are currently waiting on a redraw 
//		@group	draw
//<
clearRedrawQueue : function () {
    isc.EH._setThread("RDQ");

    //>DEBUG
    var start = isc.timeStamp();
    //<DEBUG

	// set the timer to null so that a new timer can be started if further redraws are scheduled.
    // We don't have to clearTimeout because the timeout already fired.
	this._redrawTimer = null;

	// get the list of items to be redrawn
	var list = this._redrawQueue;
    // create a new list for additional redraws (some of which may be triggered by the redraws
    // we do now!)
	this._redrawQueue = [];

	//>DEBUG
	if (this.logIsDebugEnabled()) {
		var redrawList = "";
		for (var i = 0; i < list.length; i++) {
			redrawList += list[i];
			if (i != list.length - 1) redrawList += ", ";
		}
		this.logDebug("clearRedrawQueue: " + redrawList, "drawing");
    }
	//<DEBUG

    // priorityRedraw: these items need to repaint as soon as possible, so postpone any other
    // redraws, to allow the browser to repaint the screen.
    var item, priorityList;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        if (item && item.priorityRedraw) {
            item.priorityRedraw = false; // clear the flag (it applies for one redraw only)
            if (priorityList == null) priorityList = [];
            priorityList.add(item);
            list[i] = null;
        }
    }
    if (priorityList != null) {
        //>DEBUG
        this.logInfo("Priority redraw: postponing non-priority items", "drawing");
        //<DEBUG
        this._redrawQueue = list;
        this.scheduleRedraw(list[0]); // HACK kick off the timer
        list = priorityList;
    }
	
	// now redraw each item in the list
    var redraws = 0, item;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        // ignore items that were destroyed right after being marked for redraw
        if (item == null || item.destroyed) continue;
		// avoid redrawing if an item has already been redrawn
		if (item && item.isDirty()) {
            // redraw the item
			item.redraw(false);
            redraws++;
		}
	}	
    //>DEBUG
    if (this.logIsDebugEnabled("redraws")) {
        this.logDebug("clearRedrawQueue: " + redraws + " redraws (" + list.length + " items), " +
                      (isc.timeStamp() - start) + "ms"
                      //+ " queue was: " + list
                      , "redraws");
    }
    //<DEBUG
    isc.EH._clearThread();
},




// Delayed adjustOverflows
// --------------------------------------------------------------------------------------------
// Add a canvas to the queue to have overflow adjusted after a delay, and set timer running (if
// necessary)
// See comments in 'adjustOverflow()' for description of why this function is used.
_queueForDelayedAdjustOverflow : function (canvasID) {
    if (!isc.Canvas._delayedAdjustOverflowQueue) isc.Canvas._delayedAdjustOverflowQueue = [];
    isc.Canvas._delayedAdjustOverflowQueue.add(canvasID);

    if (!isc.Canvas._delayedAdjustTimer) {
        isc.Canvas._delayedAdjustTimer = 
            isc.Timer.setTimeout({target:isc.Canvas, methodName:"_clearDelayedAdjustOverflowQueue"},
                                 isc.Canvas._delayedAdjustOverflowQueueDelay)
    }        
        
},

// Adjust overflows of all the canvii in the 'delayedAdjustOverflowQueue'
_clearDelayedAdjustOverflowQueue : function () {
    var array = isc.Canvas._delayedAdjustOverflowQueue;

    // clear the queue and the timer pointer
    isc.Canvas._delayedAdjustOverflowQueue = [];
    isc.Canvas._delayedAdjustTimer = null;

    if (!array || array.length == 0) return;
    
    for (var i = 0; i < array.length; i++) {
        // call adjustOverflow on each widget in the queue.
        // Note - if the Canvas still can't successfully adjustOverflow(), that method will 
        // re-queue the widget for delayed adjustOverflow().
        var canvas = window[array[i]];
        if (isc.isA.Canvas(canvas)) canvas.adjustOverflow("delayed");
    }
    
},

// --------------------------------------------------------------------------------------------


//>	@classMethod	Canvas.scheduleDestroy()	(A)
// 			Add a canvas that needs to be destroyed to the destroyQueue so it will be destroyed
//          automatically.  Called by Canvas.markForDestroy()
//		@group	draw
//
//		@param	canvas		(Canvas)		Canvas to be destroyed
//<
_destroyQueue:[],					
_destroyQueueDelay:0,
_$clearDestroyQueue:"clearDestroyQueue",
scheduleDestroy : function (canvas) {
    
    if (!canvas || canvas.destroyed || canvas.destroying || !canvas.destroy) return;
    
    this._destroyQueue.add(canvas);
    
	// and start the timer to destroy the objects in the queue
	if (!this._destroyTimer) {
		this._destroyTimer = 
            isc.Timer.setTimeout({target:isc.Canvas, methodName:this._$clearDestroyQueue}, this._destroyQueueDelay);
	}
},

//>	@classMethod	Canvas.clearDestroyQueue()	(A)
// 		Destroy all the canvases that are currently waiting on a destroy() 
//		@group	draw
//<
clearDestroyQueue : function () {
    isc.EH._setThread("DSQ");

    //>DEBUG
    var start = isc.timeStamp();
    //<DEBUG

	// set the timer to null so that a new timer can be started if further destroys are scheduled.
    // We don't have to clearTimeout because the timeout already fired.
	this._destroyTimer = null;

	// get the list of items to be destroyed
	var list = this._destroyQueue;
    
    // create a new list for additional destroys
	this._destroyQueue = [];

	//>DEBUG
	if (this.logIsDebugEnabled("destroys")) {
		var destroyList = "";
		for (var i = 0; i < list.length; i++) {
			destroyList += list[i];
			if (i != list.length - 1) destroyList += ", ";
		}
		this.logDebug("clearDestroyQueue: " + destroyList, "destroys");
    }
	//<DEBUG

	// destroy each item in the list
    var destroys = 0, item;
	for (var i = 0; i < list.length; i++) {
        item = list[i];
        // ignore items that are already destroyed
        if (item == null || item.destroyed || item.destroying) continue;
        item.destroy(false);
        destroys++;
	}
    //>DEBUG
    if (this.logIsDebugEnabled("destroys")) {
        // this statistic may be misleading since we may include children or peers of items already
        // in the list, in which case they'll be destroyed, but the count won't be incremented
        this.logDebug("clearDestroyQueue: " + destroys + " direct destroy() calls (" + list.length + " items), " +
                      (isc.timeStamp() - start) + "ms"
                      //+ " queue was: " + list
                      , "destroys");
    }
    //<DEBUG
    isc.EH._clearThread();
},



// helper method used to outset or inset a canvas by a certain number of pixels.
outsetRect : function (rect, outset) {
    if (!outset) return rect;

    // rect can be like the output of Canvas.getRect()
    if (isc.isAn.Array(rect)) {
        rect[0] -= outset;
        rect[1] -= outset;
        rect[2] += 2*outset;
        rect[3] += 2*outset;
        return rect;
    }
    // or rect can be a properties block
    rect.left -= outset;
    rect.top -= outset;
    rect.width += 2*outset;
    rect.height += 2*outset;
    return rect;
},

// helper: returns true if rect1 and rect2 intersect, false othewise
rectsIntersect : function (rect1, rect2) {
    
    var left1 = rect1[0],
        top1 = rect1[1],
        width1 = rect1[2],
        height1 = rect1[3],

        left2 = rect2[0],
        top2 = rect2[1],
        width2 = rect2[2],
        height2 = rect2[3];

    return !((left1 > left2 + width2) || (left1 + width1 < left2))
            && !((top1 > top2 + height2) || (top1 + height1 < top2));

},


_forceNativeTabOrderUpdate : function () {
    if (!this.__tabIndexRefreshDiv) {
        this.ns.Element.createAbsoluteElement(
            "<DIV ID='_isc_tabIndexRefreshDiv'" +
            " style='position:absolute;left:0px;top:-100px'>&nbsp;</DIV>");
        this.__tabIndexRefreshDiv = document.all["_isc_tabIndexRefreshDiv"];        
    } else {
        this.__tabIndexRefreshDiv.innerHTML = "&nbsp;"
    }        
},

// maintain a list of top level canvii - this simplifies iterating through all the canvii
// in the same parent as a widget (commonly required for zIndices)
_topCanvii : [],
_addToTopLevelCanvasList : function (canvas) {
    if (!isc.isA.Canvas(canvas) || canvas._topCanviiIndex != null) return;
    
    this._topCanvii.add(canvas);
    canvas._topCanviiIndex = this._topCanvii.length - 1;
},

_removeFromTopLevelCanvasList : function (canvas) {
    if (!isc.isA.Canvas(canvas) || canvas._topCanviiIndex == null) return;
    
    this._topCanvii[canvas._topCanviiIndex] = null;
    canvas._topCanviiIndex = null;
},



// ClickMask
// --------------------------------------------------------------------------------------------

// NOTE: BackCompat only.  Canvas instance methods should be used instead (because they provide
// more context), or for very advanced callers, the EventHandler APIs should be used directly.
showClickMask : function (clickAction, mode, unmaskedTargets) {
    return this.ns.EH.showClickMask(clickAction, mode, unmaskedTargets);
},
hideClickMask : function (ID) { this.ns.EH.hideClickMask(ID); },

// ----------------------------------------------------------------------------------------

// _placeRect() - place one rectangle adjacent to another, on a specified side, without going
// offscreen.  Takes:
// - size of rectangle to place
// - coordinates / size for rectangle to place near
// - a side
// Returns X/Y coords
// Other params:
// [canOcclude]    (boolean)   
//          This property controls whether this canvas can be positioned on top of the other 
//          widget if there isn't room to put it next to the other widget without going off
//          screen.<br>
//          If 'canOcclude' is true, simply shift this widget over the other widget, so that 
//          it ends up onscreen.  If 'canOcclude' is false, avoid extending offscreen 
///         by positioning this widget on the other side of the other widget.
// [otherAxisAlign]    (string)    
//   Can be one of "left", "right", "outside-left", "outside-right", "top", "bottom", 
//   "outside-top", "outside-bottom". (Defaults to "left" if side is "top" or "bottom", 
//   "top" if side is "left" or "right").
//   This property determines how this widget will be aligned with the other widget on the 
//   other axis.
// If there isn't enough room to avoid the widget going offscreen on one axis or the other,
// allow it to push offscreen on the bottom / left side, since we can always scroll in that 
// direction.
_placeRect : function (width, height, adjacentRect, side, canOcclude, otherAxisAlign) {
    // Default any optional params / normalize into expected structures
    
    if (isc.isAn.Array(adjacentRect)) {
        adjacentRect = {left:adjacentRect[0], top:adjacentRect[1],
             width:adjacentRect[2], height:adjacentRect[3]};

    // if passed no target rect, use the mouse position
    } else if (adjacentRect == null) {
        adjacentRect = {
            left:this.ns.EH.getX(), top:this.ns.EH.getY()
        }
    }

    // [if target rect has no width/height assume to be a point]
    if (adjacentRect.width == null) adjacentRect.width = 0;
    if (adjacentRect.height == null) adjacentRect.height = 0;
    
    // default side to "bottom"
    if (side == null) side = "bottom";
    
    // default canOcclude to true
    if (canOcclude == null) canOcclude = true;
    
    // we are placing the widget on a particular side; otherAxisAlign specifies where along
    // that side we want the widget to appear.  For example for a widget placed on the top or
    // bottom side, options in left-right order are "outside-left", "left" (aka "inside-left"),
    // "right" (aka "inside-right"), "outside-right".  Analogous choices for vertical placement
    // when placing on right/left side.
    // If unset, (or set to a value on the wrong axis), default to "left" when side is bottom/
    // top, and "top" when side is left/right
    var vertical = (side == "bottom" || side == "top");
    if (vertical) {
        // only 4 options on each axis - setting to "top" / "bottom" has no meaning if placing
        // above/below
        if (otherAxisAlign == "inside-right") otherAxisAlign = "right";
        if (otherAxisAlign != "right" && 
            otherAxisAlign != "outside-right" && 
            otherAxisAlign != "outside-left") otherAxisAlign = "left";
    } else {
        if (otherAxisAlign == "inside-bottom") otherAxisAlign = "bottom";
        if (otherAxisAlign != "bottom" && 
            otherAxisAlign != "outside-bottom" && 
            otherAxisAlign != "outside-top") otherAxisAlign = "top";
    }

    var left = adjacentRect.left;
    if (vertical) {
        // if otherAxisAlign is "left", we want to put it at the left edge (no change to left)
        // Adjust for other options:
        if (otherAxisAlign == "right") left += (adjacentRect.width - width);
        else if (otherAxisAlign == "outside-right") left += adjacentRect.width;
        else if (otherAxisAlign == "outside-left") left -= width;
    } else {
        if (side == "left") left -= width;
        else left += adjacentRect.width
    }
    
    var top = adjacentRect.top;
    if (vertical) {
        if (side == "top") top -= height;
        else top += adjacentRect.height;
    } else {
        if (otherAxisAlign == "bottom") top += (adjacentRect.height - height);
        else if (otherAxisAlign == "outside-bottom") top += adjacentRect.height;
        else if (otherAxisAlign == "outside-top") top -= height;

    }

    // left / top now represent the desired position.  Adjust this to avoid the placed rect
    // from sticking offscreen if necessary.
    // Note: If canOcclude is true, this is simple, we will just move it back as far as 
    // necessary to avoid being clipped by the browser viewport.
    // If canOcclude is false, we must "jump" across the adjacentRect to avoid covering it,
    // so we will try placing it on the opposite side, instead.
    var pageWidth = isc.Page.getWidth(),
        pageHeight = isc.Page.getHeight(),
        pageScrollLeft = isc.Page.getScrollLeft(),
        pageScrollTop = isc.Page.getScrollTop()
    ;
    
    // calculate how much we're jutting out beyond the browser viewport in each dimension
    var leftExcess = pageScrollLeft - left,
        rightExcess = left + width - (pageWidth + pageScrollLeft),
        topExcess = pageScrollTop - top,
        bottomExcess = top + height - (pageHeight + pageScrollTop);
    ;
    
    // Shortcut: if the rectangle will be completely onscreen, just return it:
    if (leftExcess <=0 && rightExcess <=0 && topExcess <=0 && bottomExcess <=0) {
        return [left, top];
    }

    // for each direction we extend out of the viewport:
    // - if we are allowing occlusion, just move top and left until not sticking out of the
    //   viewport
    // - otherwise, try moving to the other side of the adjacent rect, and use that position if
    //   it prevents sticking out of the viewport.  If moving to the other side still has us
    //   sticking out of the viewport, always prefer sticking out to the right/bottom, since
    //   the user can scroll in that direction.

    // -- HORIZONTAL ADJUSTMENTS:        
    // jutting out to the left
    if (leftExcess > 0) {
        // If we're on the left side, and canOcclude is false, we want to jump to the right 
        // side of the adjacentRect
        if (side == "left" && !canOcclude) {
            // Edge cases [no pun intended]:
            // - the adjacentRect is completely offscreen to the left
            //   * In this case, we will move past the right edge to ensure our rect is 
            //     onscreen [move to pageScrollLeft]
            // - positioning at the right edge of adjacentRect will push our new rect offscreen
            //   to the right
            //   * This is ok - preferable to be offscreen on the right since the user can 
            //     always scroll to reach it
            // - right edge of adjacentRect is offscreen on the right
            //   * not clear what's the best behavior here - for now we'll position at the
            //     right edge of adjacentRect, even though that is offscreen, as we know we
            //     can scroll it into view.
            if (adjacentRect.left + adjacentRect.width < pageScrollLeft) {
                left = pageScrollLeft;
            } else {
                left = adjacentRect.left + adjacentRect.width;
            }
        } else {
            // Just slide into view on the page
            left = pageScrollLeft;
        }

    // jutting out to the right
    } else if (rightExcess > 0) {

        // if we're on the right edge, and can't occlude, jump over the adjacentRect and
        // put on the left edge (unless this would push it out of the viewport to the left)
        if (side == "right" && !canOcclude) {
            if ((adjacentRect.left - width) >= pageScrollLeft) {
                // if the adjacent rect is completely offscreen to the right, slide into view
                // on the right edge of the screen
                if (adjacentRect.left > (pageScrollLeft + pageWidth)) 
                    left = (pageScrollLeft + pageWidth) - width;
                else left = adjacentRect.left - width;
            }
            // If putting on the left edge would push the element out of the viewport on the 
            // left, just leave on the right edge.
        } else {
            // If the object is wider than the page, just plonk it on the left edge of the 
            // page (will continue to jut out to the right)
            // Otherwise align the right edge with the right edge of the page
            if (pageWidth < width) {
                left = pageScrollLeft;
            } else {
                left = pageScrollLeft + pageWidth - width;
            }
        }
    }
    
    // -- VERTICAL ADJUSTMENTS:
    // [see comments on horizontal adjustments - identical logic]    
    // Clipped by top of viewport
    if (topExcess > 0) {
        if (side == "top" && !canOcclude) {
            if (adjacentRect.top + adjacentRect.height < pageScrollTop) {
                top = pageScrollTop;
            } else {
                top = adjacentRect.top + adjacentRect.height;
            }
        } else {
            // Just slide into view on the page
            top = pageScrollTop;
        }

    // clipped by bottom of viewport
    } else if (bottomExcess > 0) {

        if (side == "bottom" && !canOcclude) {
            if ((adjacentRect.top - height) >= pageScrollTop) {

                if (adjacentRect.top > (pageScrollTop + pageHeight)) 
                    top = (pageScrollTop + pageHeight) - height;
                else top = adjacentRect.top - height;
            }
            // If putting on the top edge would push the element out of the viewport on the 
            // top, just leave on the bottom edge.
        } else {

            if (pageHeight < height) {
                top = pageScrollTop;
            } else {
                top = pageScrollTop + pageHeight - height;
            }
        }
    }
    return [left, top];
   
},





// clean up on unload
_handleUnload : function () {
    //>IE
    if (isc.Browser.isIE) this._clearDOMHandles(); //<IE

    var logViewer = isc.Log.logViewer;
    if (logViewer && logViewer.logWindowLoaded()) {
        logViewer._logWindow.openerUnloading();
        
        logViewer._logWindow = null;
    }
}

//>IE

,
_clearDOMHandles : function () {

    // get the list of global ID objects
    var list = this._canvasList;
    // now for each item that has a _handle, clear the pointers in both directions
    for (var i = 0; i < list.length; i++) {
        var canvas = list[i];
        // if the canvas exists...
        if (canvas) {
            // ...and has a handle, remove the references to and from the DOM
            if (canvas._handle) {
                // kill the reference from the DOM to JS
                canvas._handle.eventProxy = null;
                // kill the reference from JS to the DOM
                canvas._handle = null;	
            }
        }
    }
    return true;
}
//<IE
,
//> @classMethod snapToEdge()
// consolidate logic for snapTo code. Aligns snapRect to targetRect base on parameters 
// snapTo and snapEdge.
// @param targetRect - canvas to snap to, or array of coords [left, top, width, height]
// @param snapTo - edge against which to snap
// @param snapRect - canvas being snapped
// @param snapEdge - edge of snapRect to align with snapTo
//<
snapToEdge : function (targetRect, snapTo, snapRect, snapEdge) {
    // any combo of snapTo and snapEdge can be resolved by two fairly simple coordinate 
    // transforms. SnapPoints are the 8 possible values for snapTo and snapEdge.
    // To get the final (top,left) of the canvas in question:
    //    1. find the coordinates of the snapPoint on this.parent/master given in this.snapTo
    //    2. map to the origin of this, starting from the snapPoint on this given in 
    //      this.snapEdge.Use the coordinates from 1 as the location of this.snapEdge. 
    //    3. move this to the resulting coordinates.    
    
    // If we're snapping to an edge within our parent, use internal sizing 
    // determine origin for first transform (inside borders, sb's etc).
    // Param targetRect can also be an array [left, top, width, height] 
    var targetDims, insideCoords, targetOrigin;
    if (isc.isAn.Array(targetRect)) {
        insideCoords = false;
        targetOrigin = [targetRect[1], targetRect[0]];
        targetDims = [targetRect[2], targetRect[3]];
    } else if (snapRect.masterElement) {
        insideCoords = (snapRect.percentBox == snapRect._$viewport),
        targetDims = [insideCoords ? targetRect.getViewportWidth() : 
                                      targetRect.getVisibleWidth(),
                       insideCoords ? targetRect.getViewportHeight() : 
                                      targetRect.getVisibleHeight() ];
        targetOrigin = [targetRect.getTop() + (insideCoords ? 
                                           (targetRect.getTopBorderSize() + targetRect.getTopMargin()) : 
                                          0), 
                        targetRect.getLeft() + (insideCoords ? 
                                       (targetRect.getLeftBorderSize() + targetRect.getLeftMargin()) : 
                                       0)                          
                        ];
    } else {
        insideCoords = true;
        targetDims = [targetRect.getViewportWidth(), targetRect.getViewportHeight()];
        targetOrigin = [0, 0];
    }

    // get the coordinate on the target that we are snapping to
    var firstCoord = isc.Canvas._getSnapPoint(snapTo, targetOrigin, targetDims, false);
   
    // then modify this coordinate by our size, according to which of our edges should snap
    // to the target point
    var finalCoord = isc.Canvas._getSnapPoint((snapEdge || snapTo), firstCoord,
                                        [snapRect.getVisibleWidth(),snapRect.getVisibleHeight()], true);
    
    // note that _getSnapPoint() returns [top,left], not [left,top]
    if (snapRect.snapOffsetLeft != null) finalCoord[1] += snapRect.snapOffsetLeft;
    if (snapRect.snapOffsetTop != null) finalCoord[0] += snapRect.snapOffsetTop;
    
    // finally, move this to result coords
    snapRect.moveTo(finalCoord[1], finalCoord[0]);  
    // let master know not to resize this peer
    snapRect._resizeWithMaster = false;
},

// give a rect (coord + size) return the coordinates in that rect that correspond to the edge.
_getSnapPoint : function (edge, coord, size, getInverse) {
    var cWidth = size[0],
        cHeight = size[1];
        
    // get the amount to add or subtract to top and left for each snap point
    var delta;
    if (edge == "TL") delta = [0, 0];
    else if (edge == "T") delta = [0, cWidth / 2];
    else if (edge == "TR") delta = [0, cWidth];
    else if (edge == "R") delta = [cHeight / 2, cWidth];
    else if (edge == "BR") delta = [cHeight, cWidth];
    else if (edge == "B") delta = [cHeight, cWidth / 2];
    else if (edge == "BL") delta = [cHeight, 0];
    else if (edge == "L") delta = [cHeight / 2, 0];
    else if (edge == "C") delta = [cHeight / 2, cWidth / 2];
    else delta = [0, 0];

    delta[0] = Math.floor(delta[0]);
    delta[1] = Math.floor(delta[1]);

    // apply the appropriate transform to the parameter coordinates
    if (getInverse) return [coord[0] - delta[0], coord[1] - delta[1]];
    else return [coord[0] + delta[0], coord[1] + delta[1]];
}
});	// END isc.Canvas.addClassMethods()


//  'registerStringMethods()' - add all the instance properties that can be defined as strings
//  to evaluate (or as methods) to a central registry, together with their arguments as comma
//  separated strings.
// 
isc.Canvas.registerStringMethods({
    // NOTE: event handlers are all legal to register as string methods.  We do this below.

    // Other legal stringMethods    
    resized:"deltaX,deltaY", // note these args are intentionally not doc'd, but framework
                             // code in GR.addEmbeddedComponent() currently relies on them
    showIf:"canvas",
    childRemoved:"child,name",
    peerRemoved:"peer,name",
    deparented:"oldParent,name",
    depeered:"oldMaster,name",
    //> @method     canvas.focusChanged()
    // Notification function fired when this widget recieves or loses keyboard focus.
    // @param   hasFocus (boolean) If true this widget now has keyboard focus
    // @group focus
    // @visibility external
    //<
    focusChanged:"hasFocus",
    scrolled:null,
    // The hover event is generated by the Canvas class, so not present in EH.eventTypes.
    hover:"",
    
    //> @method Canvas.onDrop()
    // Notification method fired when the user drops another canvas onto this one. Returning
    // <code>false</code> from this method will prevent any default drop behavior from occurring
    // @return (boolean) return false to cancel default drop handling
    // @visibility sgwt
    //<
    onDrop:""
    
    
});

isc.Canvas._canvasInit = function () {
    var EH = isc.EH,
        noopHandlers = {};
    for (var eventName in EH.eventTypes) {
        // Register all events as string methods using the EventHandler's authoritative list
        this.registerStringMethods(EH.eventTypes[eventName], EH._eventHandlerArgString);

        // Make sure every event handler on all Canvas's has a NO-OP function as its default value,
        // so you don't get a JS error if you explicitly call (canvas.click()).
        var functionName = EH.eventTypes[eventName];
        if (this.getInstanceProperty(functionName) == null) {
            noopHandlers[functionName] = isc.Class.NO_OP;
        }
    }
    this.addMethods(noopHandlers);
}
isc.Canvas._canvasInit();



// Backmask
// ---------------------------------------------------------------------------------------
isc.defineClass("BackMask", "Canvas").addMethods({
    autoDraw:false,
    _isBackMask:true,
    _generated:true,
    
    useClipDiv: false,
    
    hideUsingDisplayNone: isc.Browser.isMoz,
	overflow:isc.Canvas.HIDDEN,
    contents:
     "<iframe width='100%' height='100%' border='0' frameborder='0' src=\"" +
        isc.Page.getBlankFrameURL() +

      "\" marginwidth='0' marginheight='0' scrolling='no' tabIndex='-1' tabStop='false'></iframe>",
    // custom sizing policy, to avoid the backmask "squaring-out" rounded corners.  Note
    // _sizeBackMask() currently both sizes and places BackMask, which prevent us using the
    // move-by-deltas approach of _moveWithMaster:true
    _moveWithMaster:false,
    masterMoved : function () { this.masterElement._sizeBackMask() },
    _resizeWithMaster:false,
    masterResized : function () { this.masterElement._sizeBackMask(); },
    
    draw : function (a,b,c) {
        // special suppressed flag - set by BrowserPlugin to suppress the backMask
        if (this.suppressed) return this;
        if (!this.readyToDraw()) return this;
        this.invokeSuper(isc.BackMask, this._$draw, a,b,c);
        if (this.masterElement.overflow == isc.Canvas.VISIBLE) this.masterElement._sizeBackMask();
        return this;
    },
    show : function () {
        // special suppressed flag - set by BrowserPlugin to suppress the backMask
        if (!this.suppressed) this.invokeSuper(isc.BackMask, "show");
    },

    _redrawWithMaster:false,    
    _redrawWithParent:false
});

// ScreenSpan
// ---------------------------------------------------------------------------------------
isc.defineClass("ScreenSpan", "Canvas").addMethods({
    _generated:true,

    
    
    
    getInnerHTML:function () {
        if (!this._cachedContent) {
            // In IE7, the spacerHTML doesn't block clicks on "a href" links, but the img does.
            //
            // NOTE: if you update this code, also check and update EventHandler.makeEventMask();
            this._cachedContent = isc.Browser.isIE && isc.Browser.version > 6 ?
                isc.Canvas.blankImgHTML(1600,1200) : isc.Canvas.spacerHTML(1600, 1200);
        }
        return this._cachedContent;
    },
    redrawOnResize:false,
    overflow:"hidden",

    
    hide : function (waited,b,c,d) { 
        this.resizeTo(1,1); 
        this.moveTo(null,-this.getHeight());  
        return this.invokeSuper(isc.ScreenSpan, "hide", waited,b,c,d);
    },
    
    // override show to set up 
    show : function (a,b,c,d) { 
        this.fitToScreen();  
        // set up a resize handler to keep matching screen size while we're visible
        isc.Page.setEvent(
            "resize", 
            this,
            isc.Page.FIRE_ONCE,
            "pageResized"
        );
        return this.invokeSuper(isc.ScreenSpan, "show", a,b,c,d);
    },
	
    // DEBUG: set a translucent tint to see the screenSpan while debugging
    //backgroundColor:"blue",	
	//opacity:30,						
	
    // override pageResize to resize to the scrollHeight/width of the page, and to observe
    // future page resizes
    pageResized : function () { 
        if (!this.isVisible()) return;

        // resize to the browser viewport to avoid impacting the page scrollWidth/scrollHeight 
        this.resizeTo(isc.Page.getWidth(), isc.Page.getHeight());

        // fitToScreen will calculate page size, move to zero/zero and resize to cover the page.
        this.fitToScreen();

        // ensure it resizes if the page is resized again.
        isc.Page.setEvent(
            "resize", 
            this,
            isc.Page.FIRE_ONCE,
            "pageResized"
        );
    },
    
    fitToScreen : function () {
        // size to the page content (NOTE: this isn't the same as 100%/100% size because we
        // want to cover the content that would become visible if you scrolled the browser
        // window)
        var pageWidth = Math.max(isc.Page.getWidth(), isc.Page.getScrollWidth()),
            pageHeight = Math.max(isc.Page.getHeight(), isc.Page.getScrollHeight());

        

        this.resizeTo(pageWidth, pageHeight);

        this.moveTo(0,0);
    }
});

// various methods to deal with forms - these are available as class and instance methods on
// Canvas
isc._formMethods = {
//>	@method	canvas.getForm()
//			get a form in this layer by name or number
//			returns null if form can't be found
//
//			NOTE: you're MUCH better off naming forms, since IE and Nav 
//				set the form context very differently!
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
// 
//		@return	(object)	DOM form object if found, else null
//<
getForm : function (formID) {
	if (formID && typeof formID == "object") return formID;

    var theForm;
    if (formID != null && isc.Browser.isDOM) {
        // try looking up the form by ID attribute
        theForm = document.getElementById(formID);
    }
    if (theForm != null) return theForm;
        
    // try looking up the form via document.forms

    // default the formID parameter to the first form
    if (formID == null) formID = 0;

	// look for forms in the global document
	if (theForm == null) return document.forms[formID]; 
	return theForm;
},


//>	@method	canvas.getFormElementValue()
//			get a form element's value
//		does the right thing for text fields, checkboxes, radio button and selects
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
//		@param	elementID	(number or string)	name or index number of the form element to get
//		@return				(any)		value of the element or null if form or element can't be found
//<
getFormElementValue : function (formID, elementID) {
	// get the form element -- if not found, bail
	var element = this.getFormElement(formID, elementID);
	if (!element) return;
	
	// now set the value according to the element type
	switch (element.type) {
	  case "radio":
		return (element.checked ? element.value : null)

	  case "checkbox":
		return element.checked;

	  case "select-one":
	  	if (!element.options || element.options.length == 0) return null;
	  	
		// get the option that's selected
		var option = element.options[element.selectedIndex];
		// if it has a value, return the value, otherwise return the name
		return option.value;//(option.value == null ? option.value : option.text);
	
	  case "select-multiple":
		var output = [];
		for (var i = 0, len = element.options.length; i < len; i++) {
			var option = element.options[i];
			if (option.selected)
				output.add(option.value);
		}
		return output;
	
	  case "button":
	  case "reset":
	  case "submit":
	  	return null;
	
	  default:
	  	// for text field, passwords, textAreas, etc. just return the value
		return element.value;
	}
},

//>	@method	canvas.getFormValues()
// Returns an object literal with formElement names to formElement values
// returns null if form can't be found
//
//		@group	form
//
//		@param	formID		(number or string)	name or index number of the form to get
// 
//		@return	(object)	key/value pairs
//<
getFormValues : function (formID) {
	var theForm = this.getForm(formID);
	if (!theForm) return null;

	var formData = {};
    if(!theForm.elements) {
        this.logWarn("Form '"+formID+"' contains no elements - returning empty map for data.");
        return {};
    }
	for (var i = 0; i < theForm.elements.length; i++) {
		var formElement = theForm.elements[i];
		if (formElement.name != null) {
			var elementValue = this.getFormElementValue(theForm, theForm.elements[i]);
			if (elementValue != null) formData[formElement.name] = elementValue;
		}
	}
	return formData;
},



//>	@method	canvas.getFormElement()
//			get a form element by form name and element name
//		@group	form
//
//		@param	formID		(number or string or form)	name or index number of the form to get
//				returns null if form can't be found
//		@param	elementID		(number or string or form element)	name or index number of the form element to get
//		@return	(object)	form element object if found, else null
//<
getFormElement : function (formID, elementID) {
	// if they passed an element, just return it
	if (typeof elementID == "object") return elementID;
	// get the handle to the form
	var form = this.getForm(formID);
	// if the form was found, return the element if it can be found
	if (form) return form.elements[elementID];
	// otherwise return null
	return null;
}

};

isc.Canvas.addClassMethods(isc._formMethods);
isc.Canvas.addMethods(isc._formMethods);

//> @classMethod isc.setAutoDraw()
// Set the global default setting for +link{Canvas.autoDraw}.
// <p>
// After calling <code>isc.setAutoDraw()</code>, any newly created Canvas which is not given an
// explicit setting for +link{canvas.autoDraw,autoDraw} will follow the new default setting.
// <P>
// autoDraw:false is the recommended default setting for most applications since it ensures
// that extra draws will not occur when developers inadvertently omit the autoDraw:false
// setting on child components.
//
// @param [enable] whether autoDraw should be enabled or disabled.  Defaults to true.
// @see attr:Canvas.autoDraw
// @group autoDraw
// @visibility external
//<
isc.setAutoDraw = function (enable) {
    if (enable == null) enable = true;
    isc.Canvas.addProperties({
        autoDraw:enable
    });
};




//	END package Canvas
//
////////////////////


// native size reporting issues
// ---------------------------------------------------------------------------------------
// ISC needs to know how big content has drawn, for many reasons, including:
// - knowing when to introduce scrollbars and how large the scrolling region is
// - resizing overflow:visible elements to their drawn size, since otherwise the borders and
//   background color extend only to the specified size
// - laying out widgets
// - auto-sizing things to their minimum size
//
// We rely on the browser to correctly report sizes via properties like scrollWidth/Height and
// offsetWidth/Height.  
// 
// The issue: we must specify a size (it affects how content is flowed), but specifying a size
// ruins size reporting (see below).  So we use two divs: the inner one (contentDiv)
// contains the actual content we want to draw, and is not given a width or height setting, so
// it reports actual drawn size via either scrollHeight/Width or offsetHeight/Width.  The outer
// one (clipDiv) has a width and height set, which causes the contentDiv's content to flow as
// though it had its width and height set. 
//
// We have separate getHandle() and getClipHandle() calls, where the clipHandle is used for
// all positioning, clipping, sizing and visibility and the "handle" is used for the rest.
//
// scrollWidth/Height findings
// ------------------------------------
// In Moz (FF2.0) and Opera (9.20), 
//
// - overflow:visible DIVs 
//   - specified size
//     - width: report that size regardless of contained content (smaller or larger)
//     - height: 
//       - Opera: report larger but not smaller content height
//       - Moz: report the specified size regardless
//   - no specified size
//     - width: fill container, reports container's size regardless of content
//     - height: report true content height (smaller or larger)
//
// - overflow:hidden DIVs
//   - no specified size
//     - width: fill container, 
//       - Opera: reports true content size
//       - Moz: reports container size 
//     - height: reports true content size (smaller and larger)
//   - specified size
//     - reports content size only if larger, otherwise specified size
//     - exception: Opera bug: if height is specified, width goes to 0 if content is smaller
//         than specified width
//
// - double DIVs, both overflow:visible, specified sizes
//   - height reliable (smaller and larger), width always specified size
//
// - double DIVs, outer overflow:hidden, specified sizes
//   - height reliable (smaller and larger)
//   - width reliable for content larger than specified, otherwise reports specified size
//
// In sum:
// - overflow:visible DIVs, even if nested, won't work.  We couldn't detect horizontal
//   overflow.
// - a single overflow:hidden DIV would work for Moz iff we don't care about detecting
//   scrollHeight shorter than specified size.  Can't work for Opera
//
// Other notes:
// - in older Moz, overflow:hidden was not considered a scrolling region, and would report the
//   same as for overflow:visible.  However there was a special overflow setting
//   -moz-scrollbars-none that would report sizes like a scrolling region, without showing
//   scrollbars
// - in very old Moz (pre-1.0), there is a complete absence of the scrollHeight/Width
//   properties on non-scrolling regions, but offsetHeight/Width are equivalent
// - in Netscape6, even scrolling DIVs do not report scrollHeight/Width at all.
//
// offsetWidth/Height
// ------------------------------------
// offsetHeight/Width can be used as a way to determine sizes, except that:
// - if you specify a width or height for an element, Moz will report the specified height or
//   width rather than the actual drawn height or width
// - the offsetHeight is the height with respect to the offsetParent, which isn't guaranteed to be
//   any particular element (see "Widget Positioning and Sizing Methods" comment) 
// - even when the above two circumstances are worked around, the offsetHeight does not seem to
//   always be reliable.  It's not entirely clear what situations this occurs in, and the
//   behavior definitely differs between minor version of Moz (eg 1.2 vs 1.3).  A workaround of
//   setting "FLOAT:LEFT" on the contentDiv seems to solve all instances of this, but
//   introduces various problems:
//   - Float:left can cause centered content to be flush left
//   - Charts draw incorrectly on redraw, with identical HTML, even if the HTML is placed in
//     another Canvas (thus not even dynamically generated)
//   - Safari, if you put in IFRAME inside a DIV with float:left set, the IFRAME's contents
//     (result of the "src" property) doesn't seem to show up at all (not clear why).  Therfore
//     if we are loading content into an IFRAME, don't write out the FLOAT:left in Safari (it
//     shouldn't be required in any case since we're sizing the IFRAME to be 100% of the
//     conaining DIV, so we'll never need the scroll width)
//   - Mozilla Firefox (PR1.0) simlarly has problems with an IFRAME inside a DIV.
//     in this browser, the IFRAME renders, but seems to always be 300px wide if the parent
//     DIV (the content DIV for the Canvas) has FLOAT:left specified.
//     Disabling float:left in this browser if we are loading content into an IFRAME is
//     specified 
//     Note: We should re-test whether this setting is necessary at all in Moz Firefox
//   - percent-sized elements get the wrong size when placed inside a floated DIV.
//     - early Safari: 100% height is taken to be full page size
//     - Moz: 100% width: draws minimum width for content, with misaligned table columns if a
//       table
//       - goes away if content has pos:abs and pos:relative is removed from the floated DIV
//     - IE: 100% height: draws minimum height
//
// The Moz zero-width table content bug
// ------------------------------------
// Affects: Moz 1.0 - 1.6, solved in Moz 1.7 (gecko 20040616)
//
// If you access certain size-related properties in the DOM while there is an open <TABLE> tag,
// Moz will render the table cell as zero-width, regardless of it's content, which will spill
// out of the cell and overlap other cells.
// - Access window.innerHeight/innerWidth
//   - seen on 
//     - NS6: OK
//     - Moz 1.0 -> 1.1
//       - hosed if content is a DIV whose specified size is substantially larger than the contained
//         text
//     - Moz 1.2 -> 1.3
//       - hosed if content is a DIV with CSS property MARGIN set, even if set to 0
//   - We workaround this issue by having Page.getHeight() only check the DOM for window size on
//     page load / resize, and cache that value
// - Access padding-left (or -right, -top, -bottom) for any element via getComputedStyle()
//   - seen on:
//     - NS6 -> Moz 1.1: OK
//     - Moz 1.2 -> 1.3
//       - hosed if content is a DIV with CSS property MARGIN set, even if set to 0
//   - this means widgets within TABLEs can't use margins 
// - Access any size-related property on an element that has just been drawn
//   - this can no longer be reproduced, but we still delay adjustOverflow til page load just in
//     case
//
// Test file: QA/Native/mozZeroWidthBug.html
//
// --------------------------------------------------------------------------------------------
// Safari sizing issues:
//
// scrollHeight and scrollWidth appears to be available on all elements in Safari.  For 
// overflow:scroll and overflow:auto regions this reports the size we're interested in.
// For overflow:visible regions (WITH NO SPECIFIED SIZE), it also reports the 
// appropriate size, but for overflow:hidden / visible regions, with a specified size it will
// report the specified size of the handle.
// The double div solution works around this by not sizing the inner content handle, and setting
// float:left on it (required to allow scrollWidth to be reported correctly - not clear why)

//> @groupDef noFrames
// Loading the SmartClient framework into multiple frames or iframes within the same browser is
// not a supported configuration, or more accurately, not a <i>supportable</i> configuration,
// for the following reasons:
// <ul>
// <li> each additional frame multiplies the memory footprint and reduces speed
// <li> having multiple frames prevents drag and drop between components in different frames
// <li> modality handling (eg modal dialogs) doesn't automatically take into account multiple
// frames (consider tabbing order, nested modality and other issues, you'll see it's not
// realistic to provide automatic cross-frame modality handling)
// <li> inter-frame communication triggers several browser bugs: memory leaks, performance
// issues, intermittent crashes in some browsers, inconsistencies in basic JavaScript operators
// such as "typeof", and problems with form focus handling in IE, among many other bugs
// </ul>
// None of these problems are specific to SmartClient.  They happen with Ajax frameworks in
// general as well as other RIA technologies.  This is why no successful Ajax application has
// ever used the approach of double-loading a component framework into multiple frames.
// <P>
// The recommended +link{smartArchitecture,SmartClient Architecture} involves loading as many
// SmartClient-based application views as possible in the first page load, then showing and
// hiding different views as the user navigates through the application.
// <P>
// If, for whatever reason, you cannot follow the SmartClient Architecture and must load new
// SmartClient-based views by contacting the server each time, use the +link{ViewLoader} class
// to load new views, never frames.
// <P>
// Note that the use of IFrames is appropriate in certain circumstances, including loading
// certain types of content within an +link{HTMLFlow,contentsType,HTMLFlow}.  The only
// prohibited usage is loading the SmartClient framework into multiple frames within the same
// browser.
//
// @title Don't Misuse Frames
// @visibility external
//<

//> @type SCImgURL
// Properties that refer to images by URL, such as +link{Img.src} and +link{Button.icon}, are
// specially interpreted in SmartClient to allow for simpler and more uniform image URLs,
// and to allow applications to be restructured more easily.
// <P>
// <b>the application image directory</b>
// <P>
// When specifying URLs to image files via SmartClient component properties such as
// +link{StretchImg.src}, any relative path is assumed to be relative to the "application image
// directory" (<code>appImgDir</code>).  The application image directory can be set via
// +link{Page.setAppImgDir()}, and defaults to "images/", representing the typical practice of
// placing images in a subdirectory relative to the URL at which the application is accessed.
// <P>
// For applications that may be launched from multiple URLs, the <code>appImgDir</code> can be 
// set to the correct relative path to the image directory by calling
// +link{Page.setAppImgDir()} before any SmartClient components are created.  This enables
// applications or components of an application to be launched from multiple locations, or to
// be relocated, without changing any image URLs supplied to SmartClient components.
// <P>
// <b>the "[SKIN]" URL prefix</b>
// <P>
// The special prefix "[SKIN]" can be used to refer to images within the skin folder
// whenever image URLs are supplied to SmartClient components.
// <P>
// The value of "[SKIN]" is the combination of: 
// <ul>
// <li> the "skin directory", established in <code>load_skin.js</code> via +link{Page.setSkinDir()},
// plus..
// <li> the setting for +link{canvas.skinImgDir,skinImgDir} on the component where you set an
// image URL property
// </ul>
// <code>skinImgDir</code> defaults to "images/", so creating an +link{Img} component with
// +link{Img.src} set to "[SKIN]myButton/button.gif" will expand to <code>Page.getSkinDir() +
// "/images/myButton/button.gif"</code>.
// <P>
// Some components that use a large number of images use <code>skinImgDir</code> to group them
// together and make it possible to relocate all the media for the component with a single setting.
// For example, the +link{TreeGrid} class sets <code>skinImgDir</code> to "images/TreeGrid/".
// This allows +link{treeGrid.folderIcon} to be set to just "[SKIN]folder.gif" but refer to
// <code>Page.getSkinDir() + "/images/TreeGrid/folder.gif"</code>.
// <P>
// A custom subclass of TreeGrid can set <code>skinImgDir</code> to a different path, such as 
// "/images/MyTreeGrid", to source all media from a different location.
// <P>
// TIPS:
// <ul>
// <li> subcomponents may not share the parent component's setting for skinImgDir.  For
// example, the +link{window.minimizeButton} has the default setting for "skinImgDir"
// ("images/"), so the +link{img.src,src} property used with this component is set to
// "[SKIN]/Window/minimize.png" (in the "SmartClient" sample skin).
// <li> for a particular image, the skinImgDir setting on the component may not be
// convenient.  The prefix "[SKINIMG]" can be used to refer to <code>Page.getSkinDir() +
// "/images"</code> regardless of the setting for <code>skinImgDir</code>
// </ul>
// <B>Stateful image URLs</B>
// <P>
// Many image URLs in SmartClient are "stateful", meaning that the actual URL used to fetch an
// image will vary according to the component's state (eg, "Disabled"), generally, by adding a
// suffix to the image URL.  See the +link{group:skinning,Skinning Overview} for more
// information on statefulness and the +link{Img.src} documentation for information on how
// stateful image URLs are formed.
//
// @visibility external
//<

//> @groupDef skinning
// 
// Skinning (aka "theming" or "branding") is the process of modifying SmartClient's default
// look and feel to match the desired look and feel for your application.  SmartClient supports
// an extremely powerful and simple skinning system that allows designers with a basic grasp of
// CSS and JavaScript to skin any SmartClient component.
// <P>
// <h4>Basics</h4>
// <P>
// <ul>
// <li> SmartClient components create their visual appearance by dynamically generating HTML,
// within the browser, using JavaScript.
//
// <li> the HTML generated by SmartClient components contains CSS style names and URLs to
// images
//
// <li> SmartClient components can be skinned by replacing the CSS styles and images that
// the components use by default, or by using JavaScript properties to configure
// components to use new CSS styles and new image URLs.
//
// <li> You can change the appearance of an individual SmartClient component by passing 
// properties to +link{class.create,create()}, or you can skin all components of the
// same class at once, by using +link{classMethod:class.addProperties,addProperties()} and 
// +link{class.changeDefaults,changeDefaults()} to change the defaults for the class.
//
// <li> A "skin" consists of:
// <ul>
// <li> a single CSS stylesheet containing all CSS styles used by SmartClient components
// (<code>skin_styles.css</code>)
// <li> a single JavaScript file that sets component defaults (<code>load_skin.js</code>)
// <li> a directory tree of images organized by component
// </ul>
// 
// <li>
// The example skins that come with SmartClient are in
// <code>isomorphicSDK/isomorphic/skins</code>.  The standard filesystem layout for a skin is:
// <pre>
//    isomorphic/skins
//        skin_styles.css
//        load_skin.js
//        images/
//            ListGrid/
//                sort_ascending.gif
//                ...
//            Tab/
//            ... other directories containing
//                component or shared media ...
// </pre>
// <li> A skin is loaded via a &lt;SCRIPT SRC=&gt; tag that loads load_skin.js, or, if using
// the SmartClient server, by specifying the "skin" property of the +link{group:loadISCTag}.
// load_skin.js loads the stylesheet and sets the CSS styleNames and media URLs that
// SmartClient components will use.
// </ul>
// <P>
// <h4>Modifying Skins</h4>
// <P>
// To modify a skin, first create a copy of one of the skins that comes with the SmartClient
// SDK, then modify the copy.  Full instructions are provided in Chapter 9 of the 
// +docTreeLink{QuickStartGuide,QuickStart Guide}.
// <P>
// <h4>Locating Skinning Properties</h4>
// <P>
// <b>Starting from the name of the component</b>
// <P>
// Given a SmartClient component that you want to skin, use the search feature of the SmartClient
// Reference to locate it, and open the "Instance APIs" tab.
// <ul>
// <li> for properties that set CSS styles, look for properties whose name includes "style", eg
// +link{button.baseStyle}
// <li> for properties that control URLs to media, look for properties whose name includes
// "src", "image" or "icon", such as +link{Img.src}
// <li> for subcomponents that also support skinning, look for properties of type "AutoChild"
// and check the reference for the type of the AutoChild for settable properties.  For example,
// +link{window.minimizeButton} is an ImgButton and therefore supports +link{imgButton.src}.
// </ul>
// <b>TIP</b>: the Instance APIs tab allows you to search within just the current class, limit 
// the display to just properties or methods, and sort by type.
// <P>
// <b>Starting from a running example</b>
// <P>
// Open the Developer Console and use the Watch Tab to locate the component or subcomponent you 
// want to skin, then locate it in the documentation, as above.
// <P>
// If you don't find the component in the documentation, it may be a custom component specific
// to your organization.  To find the base SmartClient component for a component named
// "MyComponent", use the following code to find out the name of the superclass:
// <pre>
//     isc.<i>MyComponent</i>.getSuperClass().getClassName()
// </pre>
// Repeat this until you arrive at a SmartClient built-in class.  You can execute this code in
// the "Eval JS" area of the Results pane of the Developer Console.
// <P>
// Specific browsers offer alternate approaches to quickly discover the images or style names
// being used for a part of a SmartClient component's appearance: 
// <ul>
// <li> the Firefox browser offers a dialog via Tools->"Page Info" that gives a manifest of
// media used in the page.
// <li> the +externalLink{http://www.getfirebug.com/,Firebug} extension for Firefox has an
// "Inspect" feature that allows you to see the HTML, CSS and media in use for a given area of
// the screen
// <li> right clicking (option-click on a Mac) on an image and choosing "Properties" shows a
// dialog that provides the image URL in most browsers.  Tips:
// <ul>
// <li> if a SmartClient component is showing text over an image, right-click at the very edge of
// the underlying image to get image properties rather than information about the text label
// <li> on some browsers, in order to see the full image URL, you may need to drag select the 
// partial URL of the image shown in the properties dialog
// </ul>
// </ul>
// <P>
// <h4>Image URLs in SmartClient</h4>
// <P>
// Properties that refer to images by URL, such as +link{Img.src} and +link{Button.icon}, are
// specially interpreted in SmartClient to allow for simpler and more uniform image URLs,
// and to allow applications to be restructured more easily.
// <P>
// Unlike the URL used with an HTML &lt;IMG&gt; element, the image URL passed to a SmartClient
// component is not assumed to be relative to the current page.  See +link{type:SCImgURL} for a
// full explanation of the default application image directory, and the meaning of the "[SKIN]"
// prefix.
// <P>
// <h4>Specifying Image URLs</h4>
// <P>
// Default image URLs for SmartClient components are specified in <code>load_skin.js</code> via
// JavaScript, using calls to +link{classMethod:Class.addProperties()} and
// +link{class.changeDefaults()}.  For example, the <code>load_skin.js</code> file
// from the "SmartClient" sample skin includes the following code to establish the media used by
// +link{window.minimizeButton}:
// <pre>
//    isc.Window.changeDefaults("minimizeButtonDefaults", { 
//         src:"[SKIN]/Window/minimize.png"
//    });
// </pre>
// <P>
// <h4>Specifying Image Sizes</h4>
// <P>
// Many SmartClient components must know some image sizes in advance, in order to allow those
// components to autosize to data or content.
// <P>
// For example, the +link{ImgTab}s used in +link{TabSet}s are capable of automatically sizing
// to a variable length +link{tab.title}.  To make this possible, SmartClient must know the
// sizes of the images used as "endcaps" on each tab in advance.
// <P>
// Like image URLs, image sizes are specified in <code>load_skin.js</code>.  The following code
// sample establishes the default size of the "endcaps" for tabs, by setting a default value
// for +link{ImgTab.capSize}:
// <pre>
//     isc.ImgTab.addProperties({
//         capSize:4
//     })
// </pre>
// <P>
// <h4>CSS usage in SmartClient</h4>
// <P>
// In SmartClient, screen layout and sizing are controlled via JavaScript, and appearance via
// CSS and images.  
// <P>
// CSS borders, margins and padding applied to SmartClient components can be treated as purely
// visual properties with no effect on sizing or layout.  Unlike HTML elements, a SmartClient
// component will always have the exact size you specify via JavaScript, regardless of browser
// platform, browser compatibility mode, or borders, margins, or padding, all of which normally
// affect the final size of an HTML element. 
// <P>
// For this reason, SmartClient skinning requires only novice-level familiarity with CSS, as CSS
// is used principally for colors and fonts.  See +link{type:CSSStyleName,this discussion} for
// further details on what properties should be set via CSS vs via JavaScript.
// <P>
// <h4>Statefulness and Suffixes</h4>
// <P>
// Some components or areas within components, including buttons and the cells within a grid, are
// "stateful", meaning that they can be in one of a set of states each of which has a distinct
// visual appearance.
// <P>
// Stateful components switch the CSS styles or image URLs they are using as they transition
// from state to state, appending state information as suffixes on the style names or URL.
// See +link{img.src} and +link{button.baseStyle} for details and examples.
// <P>
// SmartClient has built-in logic to manage a series of state transitions, such as:
// <ul>
// <li> "rollover": showing a different appearance when the mouse is over a component
// <li> "button down": showing a different appearance when the mouse is pressed over a
// component
// <li> "disabled": showing a different appearance when a component cannot be interacted with
// <li> "selected": showing one of a set of components in a different state to indicate
// selection
// </ul>
// Flags on some components, such as +link{ImgButton.showRollOver}, allow you to control whether the
// component will switch CSS style or image URL when the component transitions into a given state.
// <P>
// <h4>StretchImg: 3-segment stretchable images</h4>
// <P>
// A +link{StretchImg} is SmartClient component that renders out a compound image composed of 3
// image files: two fixed-size endcaps images and a stretchable center segment.  Like stateful
// components, the names of each image segment is appended to the image URL as a suffix.  See
// +link{stretchImg.src} for details.
// <P>
// <h4>EdgedCanvas</h4>
// <P>
// Similar to a StretchImg, an +link{EdgedCanvas} provides an image-based decorative edge
// around and/or behind another component, with up to 9 segments (a 3x3 grid).  Decorative
// edges can be added to any component by setting +link{canvas.showEdges,showEdges:true}.
// EdgedCanvas is also used to construct dropshadows, which can be enabled on any component via
// +link{canvas.showShadow,showShadow:true}.
// <P>
// <h4>Multiple looks for the same component type</h4>
// <P>
// In some cases you need to create two variations in appearance for a component with the same
// behavior.  For example, you may want to create a specialized Window, called "PaletteWindow",
// that behaves like a normal Window but has a very compact look & feel.  To create a
// separately skinnable component for PaletteWindow, use +link{classMethod:isc.defineClass()}.  For
// example:
// <pre>
//    isc.defineClass("PaletteWindow", "Window");
//    isc.PaletteWindow.addProperties({
//        showFooter:false,
//        ...
//    })
// </pre>
//
// @treeLocation Concepts
// @visibility external
// @title Skinning / Theming
//<



